diff options
Diffstat (limited to 'openbsd-compat/libasr')
28 files changed, 7421 insertions, 0 deletions
diff --git a/openbsd-compat/libasr/asr.c b/openbsd-compat/libasr/asr.c new file mode 100644 index 00000000..90bc59b4 --- /dev/null +++ b/openbsd-compat/libasr/asr.c @@ -0,0 +1,867 @@ +/* $OpenBSD: asr.c,v 1.61 2018/10/22 17:31:24 krw Exp $ */ +/* + * Copyright (c) 2010-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 <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <fcntl.h> +#include <resolv.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +#include "thread_private.h" + +#define DEFAULT_CONF "lookup file\n" +#define DEFAULT_LOOKUP "lookup bind file" + +#define RELOAD_DELAY 15 /* seconds */ + +static void asr_check_reload(struct asr *); +static struct asr_ctx *asr_ctx_create(void); +static void asr_ctx_ref(struct asr_ctx *); +static void asr_ctx_free(struct asr_ctx *); +static int asr_ctx_add_searchdomain(struct asr_ctx *, const char *); +static int asr_ctx_from_file(struct asr_ctx *, const char *); +static int asr_ctx_from_string(struct asr_ctx *, const char *); +static int asr_ctx_parse(struct asr_ctx *, const char *); +static int asr_parse_nameserver(struct sockaddr *, const char *); +static int asr_ndots(const char *); +static void pass0(char **, int, struct asr_ctx *); +static int strsplit(char *, char **, int); +static void asr_ctx_envopts(struct asr_ctx *); +static void *__THREAD_NAME(_asr); + +static struct asr *_asr = NULL; + +#ifndef HAVE_ISSETUGID +#define issetugid() ((getuid() != geteuid())) +#endif + +/* Allocate and configure an async "resolver". */ +static void * +_asr_resolver(void) +{ + static int init = 0; + struct asr *asr; + + if (init == 0) { +#ifdef DEBUG + if (getenv("ASR_DEBUG")) + _asr_debug = stderr; +#endif + init = 1; + } + + if ((asr = calloc(1, sizeof(*asr))) == NULL) + goto fail; + + asr_check_reload(asr); + if (asr->a_ctx == NULL) { + if ((asr->a_ctx = asr_ctx_create()) == NULL) + goto fail; + if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1) + goto fail; + asr_ctx_envopts(asr->a_ctx); + } + +#ifdef DEBUG + _asr_dump_config(_asr_debug, asr); +#endif + return (asr); + + fail: + if (asr) { + if (asr->a_ctx) + asr_ctx_free(asr->a_ctx); + free(asr); + } + + return (NULL); +} + +/* + * Free the "asr" async resolver (or the thread-local resolver if NULL). + * Drop the reference to the current context. + */ +void +_asr_resolver_done(void *arg) +{ + struct asr *asr = arg; + struct asr **priv; + + if (asr == NULL) { + priv = _THREAD_PRIVATE(_asr, _asr, &_asr); + if (*priv == NULL) + return; + asr = *priv; + *priv = NULL; + } + + _asr_ctx_unref(asr->a_ctx); + free(asr); +} + +/* + * Cancel an async query. + */ +void +asr_abort(struct asr_query *as) +{ + _asr_async_free(as); +} + +/* + * Resume the "as" async query resolution. Return one of ASYNC_COND, + * or ASYNC_DONE and put query-specific return values in the user-allocated + * memory at "ar". + */ +int +asr_run(struct asr_query *as, struct asr_result *ar) +{ + int r, saved_errno = errno; + + DPRINT("asr: asr_run(%p, %p) %s ctx=[%p]\n", as, ar, + _asr_querystr(as->as_type), as->as_ctx); + r = as->as_run(as, ar); + + DPRINT("asr: asr_run(%p, %p) -> %s", as, ar, _asr_transitionstr(r)); +#ifdef DEBUG + if (r == ASYNC_COND) +#endif + DPRINT(" fd=%i timeout=%i", ar->ar_fd, ar->ar_timeout); + DPRINT("\n"); + if (r == ASYNC_DONE) + _asr_async_free(as); + + errno = saved_errno; + + return (r); +} +DEF_WEAK(asr_run); + +static int +poll_intrsafe(struct pollfd *fds, nfds_t nfds, int timeout) +{ + struct timespec pollstart, pollend, elapsed; + int r; + + if (clock_gettime(CLOCK_MONOTONIC, &pollstart)) + return -1; + + while ((r = poll(fds, 1, timeout)) == -1 && errno == EINTR) { + if (clock_gettime(CLOCK_MONOTONIC, &pollend)) + return -1; + timespecsub(&pollend, &pollstart, &elapsed); + timeout -= elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000; + if (timeout < 1) + return 0; + } + + return r; +} + +/* + * Same as asr_run, but run in a loop that handles the fd conditions result. + */ +int +asr_run_sync(struct asr_query *as, struct asr_result *ar) +{ + struct pollfd fds[1]; + int r, saved_errno = errno; + + while ((r = asr_run(as, ar)) == ASYNC_COND) { + fds[0].fd = ar->ar_fd; + fds[0].events = (ar->ar_cond == ASR_WANT_READ) ? POLLIN:POLLOUT; + + if (poll_intrsafe(fds, 1, ar->ar_timeout) == -1) { + memset(ar, 0, sizeof(*ar)); + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = EAI_SYSTEM; + ar->ar_rrset_errno = NETDB_INTERNAL; + _asr_async_free(as); + errno = saved_errno; + return ASYNC_DONE; + } + + /* + * Otherwise, just ignore the error and let asr_run() + * catch the failure. + */ + } + + errno = saved_errno; + + return (r); +} +DEF_WEAK(asr_run_sync); + +/* + * Create a new async request of the given "type" on the async context "ac". + * Take a reference on it so it does not get deleted while the async query + * is running. + */ +struct asr_query * +_asr_async_new(struct asr_ctx *ac, int type) +{ + struct asr_query *as; + + DPRINT("asr: asr_async_new(ctx=%p) type=%i refcount=%i\n", ac, type, + ac ? ac->ac_refcount : 0); + if (ac == NULL || (as = calloc(1, sizeof(*as))) == NULL) + return (NULL); + + ac->ac_refcount += 1; + as->as_ctx = ac; + as->as_fd = -1; + as->as_type = type; + as->as_state = ASR_STATE_INIT; + + return (as); +} + +/* + * Free an async query and unref the associated context. + */ +void +_asr_async_free(struct asr_query *as) +{ + DPRINT("asr: asr_async_free(%p)\n", as); + + if (as->as_subq) + _asr_async_free(as->as_subq); + + switch (as->as_type) { + case ASR_SEND: + if (as->as_fd != -1) + close(as->as_fd); + if (as->as.dns.obuf && !(as->as_flags & ASYNC_EXTOBUF)) + free(as->as.dns.obuf); + if (as->as.dns.ibuf) + free(as->as.dns.ibuf); + if (as->as.dns.dname) + free(as->as.dns.dname); + break; + + case ASR_SEARCH: + if (as->as.search.name) + free(as->as.search.name); + break; + + case ASR_GETRRSETBYNAME: + if (as->as.rrset.name) + free(as->as.rrset.name); + break; + + case ASR_GETHOSTBYNAME: + case ASR_GETHOSTBYADDR: + if (as->as.hostnamadr.name) + free(as->as.hostnamadr.name); + break; + + case ASR_GETADDRINFO: + if (as->as.ai.aifirst) + freeaddrinfo(as->as.ai.aifirst); + if (as->as.ai.hostname) + free(as->as.ai.hostname); + if (as->as.ai.servname) + free(as->as.ai.servname); + if (as->as.ai.fqdn) + free(as->as.ai.fqdn); + break; + + case ASR_GETNAMEINFO: + break; + } + + _asr_ctx_unref(as->as_ctx); + free(as); +} + +/* + * Get a context from the given resolver. This takes a new reference to + * the returned context, which *must* be explicitly dropped when done + * using this context. + */ +struct asr_ctx * +_asr_use_resolver(void *arg) +{ + struct asr *asr = arg; + struct asr **priv; + + if (asr == NULL) { + DPRINT("using thread-local resolver\n"); + priv = _THREAD_PRIVATE(_asr, _asr, &_asr); + if (*priv == NULL) { + DPRINT("setting up thread-local resolver\n"); + *priv = _asr_resolver(); + } + asr = *priv; + } + if (asr != NULL) { + asr_check_reload(asr); + asr_ctx_ref(asr->a_ctx); + return (asr->a_ctx); + } + return (NULL); +} + +static void +asr_ctx_ref(struct asr_ctx *ac) +{ + DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount); + ac->ac_refcount += 1; +} + +/* + * Drop a reference to an async context, freeing it if the reference + * count drops to 0. + */ +void +_asr_ctx_unref(struct asr_ctx *ac) +{ + DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac, + ac ? ac->ac_refcount : 0); + if (ac == NULL) + return; + if (--ac->ac_refcount) + return; + + asr_ctx_free(ac); +} + +static void +asr_ctx_free(struct asr_ctx *ac) +{ + int i; + + if (ac->ac_domain) + free(ac->ac_domain); + for (i = 0; i < ASR_MAXNS; i++) + free(ac->ac_ns[i]); + for (i = 0; i < ASR_MAXDOM; i++) + free(ac->ac_dom[i]); + + free(ac); +} + +/* + * Reload the configuration file if it has changed on disk. + */ +static void +asr_check_reload(struct asr *asr) +{ + struct asr_ctx *ac; + struct stat st; + struct timespec ts; + pid_t pid; + + pid = getpid(); + if (pid != asr->a_pid) { + asr->a_pid = pid; + asr->a_rtime = 0; + } + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) + return; + + if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0) + return; + asr->a_rtime = ts.tv_sec; + + DPRINT("asr: checking for update of \"%s\"\n", _PATH_RESCONF); + if (stat(_PATH_RESCONF, &st) == -1 || + asr->a_mtime == st.st_mtime || + (ac = asr_ctx_create()) == NULL) + return; + asr->a_mtime = st.st_mtime; + + DPRINT("asr: reloading config file\n"); + if (asr_ctx_from_file(ac, _PATH_RESCONF) == -1) { + asr_ctx_free(ac); + return; + } + + asr_ctx_envopts(ac); + if (asr->a_ctx) + _asr_ctx_unref(asr->a_ctx); + asr->a_ctx = ac; +} + +/* + * Construct a fully-qualified domain name for the given name and domain. + * If "name" ends with a '.' it is considered as a FQDN by itself. + * Otherwise, the domain, which must be a FQDN, is appended to "name" (it + * may have a leading dot which would be ignored). If the domain is null, + * then "." is used. Return the length of the constructed FQDN or (0) on + * error. + */ +size_t +_asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen) +{ + size_t len; + + if (domain == NULL) + domain = "."; + else if ((len = strlen(domain)) == 0) + return (0); + else if (domain[len -1] != '.') + return (0); + + len = strlen(name); + if (len == 0) { + if (strlcpy(buf, domain, buflen) >= buflen) + return (0); + } else if (name[len - 1] != '.') { + if (domain[0] == '.') + domain += 1; + if (strlcpy(buf, name, buflen) >= buflen || + strlcat(buf, ".", buflen) >= buflen || + strlcat(buf, domain, buflen) >= buflen) + return (0); + } else { + if (strlcpy(buf, name, buflen) >= buflen) + return (0); + } + + return (strlen(buf)); +} + +/* + * Count the dots in a string. + */ +static int +asr_ndots(const char *s) +{ + int n; + + for (n = 0; *s; s++) + if (*s == '.') + n += 1; + + return (n); +} + +/* + * Allocate a new empty context. + */ +static struct asr_ctx * +asr_ctx_create(void) +{ + struct asr_ctx *ac; + + if ((ac = calloc(1, sizeof(*ac))) == NULL) + return (NULL); + + ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH; + ac->ac_refcount = 1; + ac->ac_ndots = 1; +#ifndef ASR_IPV4_BEFORE_IPV6 + ac->ac_family[0] = AF_INET6; + ac->ac_family[1] = AF_INET; +#else + ac->ac_family[0] = AF_INET; + ac->ac_family[1] = AF_INET6; +#endif + ac->ac_family[2] = -1; + + ac->ac_nscount = 0; + ac->ac_nstimeout = 5; + ac->ac_nsretries = 4; + + return (ac); +} + +struct asr_ctx * +_asr_no_resolver(void) +{ + return asr_ctx_create(); +} + +/* + * Add a search domain to the async context. + */ +static int +asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain) +{ + char buf[MAXDNAME]; + + if (ac->ac_domcount == ASR_MAXDOM) + return (-1); + + if (_asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0) + return (-1); + + if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL) + return (0); + + ac->ac_domcount += 1; + + return (1); +} + +static int +strsplit(char *line, char **tokens, int ntokens) +{ + int ntok; + char *cp, **tp; + + for (cp = line, tp = tokens, ntok = 0; + ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; ) + if (**tp != '\0') { + tp++; + ntok++; + } + + return (ntok); +} + +/* + * Pass on a split config line. + */ +static void +pass0(char **tok, int n, struct asr_ctx *ac) +{ + int i, j, d; + const char *e; + struct sockaddr_storage ss; + + if (!strcmp(tok[0], "nameserver")) { + if (ac->ac_nscount == ASR_MAXNS) + return; + if (n != 2) + return; + if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1])) + return; + if ((ac->ac_ns[ac->ac_nscount] = calloc(1, SS_LEN(&ss))) == NULL) + return; + memmove(ac->ac_ns[ac->ac_nscount], &ss, SS_LEN(&ss)); + ac->ac_nscount += 1; + + } else if (!strcmp(tok[0], "domain")) { + if (n != 2) + return; + if (ac->ac_domain) + return; + ac->ac_domain = strdup(tok[1]); + + } else if (!strcmp(tok[0], "lookup")) { + /* ensure that each lookup is only given once */ + for (i = 1; i < n; i++) + for (j = i + 1; j < n; j++) + if (!strcmp(tok[i], tok[j])) + return; + ac->ac_dbcount = 0; + for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) { + if (!strcmp(tok[i], "yp")) { + /* silently deprecated */ + } else if (!strcmp(tok[i], "bind")) + ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS; + else if (!strcmp(tok[i], "file")) + ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE; + } + } else if (!strcmp(tok[0], "search")) { + /* resolv.conf says the last line wins */ + for (i = 0; i < ASR_MAXDOM; i++) { + free(ac->ac_dom[i]); + ac->ac_dom[i] = NULL; + } + ac->ac_domcount = 0; + for (i = 1; i < n; i++) + asr_ctx_add_searchdomain(ac, tok[i]); + + } else if (!strcmp(tok[0], "family")) { + if (n == 1 || n > 3) + return; + for (i = 1; i < n; i++) + if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6")) + return; + for (i = 1; i < n; i++) + ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \ + AF_INET6 : AF_INET; + ac->ac_family[i - 1] = -1; + + } else if (!strcmp(tok[0], "options")) { + for (i = 1; i < n; i++) { + if (!strcmp(tok[i], "tcp")) + ac->ac_options |= RES_USEVC; + else if (!strcmp(tok[i], "edns0")) + ac->ac_options |= RES_USE_EDNS0; + else if ((!strncmp(tok[i], "ndots:", 6))) { + e = NULL; + d = strtonum(tok[i] + 6, 1, 16, &e); + if (e == NULL) + ac->ac_ndots = d; + } + } + } +} + +/* + * Setup an async context with the config specified in the string "str". + */ +static int +asr_ctx_from_string(struct asr_ctx *ac, const char *str) +{ + char buf[512], *ch; + + asr_ctx_parse(ac, str); + + if (ac->ac_dbcount == 0) { + /* No lookup directive */ + asr_ctx_parse(ac, DEFAULT_LOOKUP); + } + + if (ac->ac_nscount == 0) + asr_ctx_parse(ac, "nameserver 127.0.0.1"); + + if (ac->ac_domain == NULL) + if (gethostname(buf, sizeof buf) == 0) { + ch = strchr(buf, '.'); + if (ch) + ac->ac_domain = strdup(ch + 1); + else /* Assume root. see resolv.conf(5) */ + ac->ac_domain = strdup(""); + } + + /* If no search domain was specified, use the local subdomains */ + if (ac->ac_domcount == 0) + for (ch = ac->ac_domain; ch; ) { + asr_ctx_add_searchdomain(ac, ch); + ch = strchr(ch, '.'); + if (ch && asr_ndots(++ch) == 0) + break; + } + + return (0); +} + +/* + * Setup the "ac" async context from the file at location "path". + */ +static int +asr_ctx_from_file(struct asr_ctx *ac, const char *path) +{ + FILE *cf; + char buf[4096]; + ssize_t r; + + cf = fopen(path, "re"); + if (cf == NULL) + return (-1); + + r = fread(buf, 1, sizeof buf - 1, cf); + if (feof(cf) == 0) { + DPRINT("asr: config file too long: \"%s\"\n", path); + r = -1; + } + fclose(cf); + if (r == -1) + return (-1); + buf[r] = '\0'; + + return asr_ctx_from_string(ac, buf); +} + +/* + * Parse lines in the configuration string. For each one, split it into + * tokens and pass them to "pass0" for processing. + */ +static int +asr_ctx_parse(struct asr_ctx *ac, const char *str) +{ + size_t len; + const char *line; + char buf[1024]; + char *tok[10]; + int ntok; + + line = str; + while (*line) { + len = strcspn(line, "\n\0"); + if (len < sizeof buf) { + memmove(buf, line, len); + buf[len] = '\0'; + } else + buf[0] = '\0'; + line += len; + if (*line == '\n') + line++; + buf[strcspn(buf, ";#")] = '\0'; + if ((ntok = strsplit(buf, tok, 10)) == 0) + continue; + + pass0(tok, ntok, ac); + } + + return (0); +} + +/* + * Check for environment variables altering the configuration as described + * in resolv.conf(5). Although not documented there, this feature is disabled + * for setuid/setgid programs. + */ +static void +asr_ctx_envopts(struct asr_ctx *ac) +{ + char buf[4096], *e; + size_t s; + + if (issetugid()) { + ac->ac_options |= RES_NOALIASES; + return; + } + + if ((e = getenv("RES_OPTIONS")) != NULL) { + strlcpy(buf, "options ", sizeof buf); + strlcat(buf, e, sizeof buf); + s = strlcat(buf, "\n", sizeof buf); + if (s < sizeof buf) + asr_ctx_parse(ac, buf); + } + + if ((e = getenv("LOCALDOMAIN")) != NULL) { + strlcpy(buf, "search ", sizeof buf); + strlcat(buf, e, sizeof buf); + s = strlcat(buf, "\n", sizeof buf); + if (s < sizeof buf) + asr_ctx_parse(ac, buf); + } +} + +/* + * Parse a resolv.conf(5) nameserver string into a sockaddr. + */ +static int +asr_parse_nameserver(struct sockaddr *sa, const char *s) +{ + in_port_t portno = 53; + + if (_asr_sockaddr_from_str(sa, PF_UNSPEC, s) == -1) + return (-1); + + if (sa->sa_family == PF_INET) + ((struct sockaddr_in *)sa)->sin_port = htons(portno); + else if (sa->sa_family == PF_INET6) + ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno); + + return (0); +} + +/* + * Turn a (uncompressed) DNS domain name into a regular nul-terminated string + * where labels are separated by dots. The result is put into the "buf" buffer, + * truncated if it exceeds "max" chars. The function returns "buf". + */ +char * +_asr_strdname(const char *_dname, char *buf, size_t max) +{ + const unsigned char *dname = _dname; + char *res; + size_t left, n, count; + + if (_dname[0] == 0) { + strlcpy(buf, ".", max); + return buf; + } + + res = buf; + left = max - 1; + for (n = 0; dname[0] && left; n += dname[0]) { + count = (dname[0] < (left - 1)) ? dname[0] : (left - 1); + memmove(buf, dname + 1, count); + dname += dname[0] + 1; + left -= count; + buf += count; + if (left) { + left -= 1; + *buf++ = '.'; + } + } + buf[0] = 0; + + return (res); +} + +/* + * Read and split the next line from the given namedb file. + * Return -1 on error, or put the result in the "tokens" array of + * size "ntoken" and returns the number of token on the line. + */ +int +_asr_parse_namedb_line(FILE *file, char **tokens, int ntoken, char *lbuf, size_t sz) +{ + size_t len; + char *buf; + int ntok; + + again: + if ((buf = fgetln(file, &len)) == NULL) + return (-1); + + if (len >= sz) + goto again; + + if (buf[len - 1] == '\n') + len--; + else { + memcpy(lbuf, buf, len); + buf = lbuf; + } + + buf[len] = '\0'; + buf[strcspn(buf, "#")] = '\0'; + if ((ntok = strsplit(buf, tokens, ntoken)) == 0) + goto again; + + return (ntok); +} + +/* + * Update the async context so that it uses the next configured DB. + * Return 0 on success, or -1 if no more DBs is available. + */ +int +_asr_iter_db(struct asr_query *as) +{ + if (as->as_db_idx >= as->as_ctx->ac_dbcount) { + DPRINT("asr_iter_db: done\n"); + return (-1); + } + + as->as_db_idx += 1; + DPRINT("asr_iter_db: %i\n", as->as_db_idx); + + return (0); +} diff --git a/openbsd-compat/libasr/asr.h b/openbsd-compat/libasr/asr.h new file mode 100644 index 00000000..e9725e6b --- /dev/null +++ b/openbsd-compat/libasr/asr.h @@ -0,0 +1,95 @@ +/* $OpenBSD: asr.h,v 1.1 2014/03/26 18:13:15 eric Exp $ */ +/* + * Copyright (c) 2012-2014 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. + */ + +/* + * Expected fd conditions + */ +#define ASR_WANT_READ 1 +#define ASR_WANT_WRITE 2 + +/* + * Structure through which asynchronous query results are returned when + * calling asr_run(). + */ +struct asr_result { + /* Fields set if the query is not done yet (asr_run returns 0) */ + int ar_cond; /* ASR_WANT_READ or ASR_WANT_WRITE */ + int ar_fd; /* the fd waiting for io condition */ + int ar_timeout; /* time to wait for in milliseconds */ + + /* Error fields. Depends on the query type. */ + int ar_errno; + int ar_h_errno; + int ar_gai_errno; + int ar_rrset_errno; + + /* Result for res_*_async() calls */ + int ar_count; /* number of answers in the dns reply */ + int ar_rcode; /* response code in the dns reply */ + void *ar_data; /* raw reply packet (must be freed) */ + int ar_datalen; /* reply packet length */ + struct sockaddr_storage ar_ns; /* nameserver that responded */ + + /* Result for other calls. Must be freed properly. */ + struct addrinfo *ar_addrinfo; + struct rrsetinfo *ar_rrsetinfo; + struct hostent *ar_hostent; + struct netent *ar_netent; +}; + +/* + * Asynchronous query management. + */ + +/* Forward declaration. The API uses opaque pointers as query handles. */ +struct asr_query; + +int asr_run(struct asr_query *, struct asr_result *); +int asr_run_sync(struct asr_query *, struct asr_result *); +void asr_abort(struct asr_query *); + +/* + * Asynchronous version of the resolver functions. Similar prototypes, with + * an extra context parameter at the end which must currently be set to NULL. + * All functions return a handle suitable for use with the management functions + * above. + */ +struct asr_query *res_send_async(const unsigned char *, int, void *); +struct asr_query *res_query_async(const char *, int, int, void *); +struct asr_query *res_search_async(const char *, int, int, void *); + +struct asr_query *getrrsetbyname_async(const char *, unsigned int, unsigned int, + unsigned int, void *); + +struct asr_query *gethostbyname_async(const char *, void *); +struct asr_query *gethostbyname2_async(const char *, int, void *); +struct asr_query *gethostbyaddr_async(const void *, socklen_t, int, void *); + +struct asr_query *getnetbyname_async(const char *, void *); +struct asr_query *getnetbyaddr_async(in_addr_t, int, void *); + +struct asr_query *getaddrinfo_async(const char *, const char *, + const struct addrinfo *, void *); +struct asr_query *getnameinfo_async(const struct sockaddr *, socklen_t, char *, + size_t, char *, size_t, int, void *); + +/* only there for -portable */ +void asr_freeaddrinfo(struct addrinfo *); + +/* from in event.h */ +struct event_asr * event_asr_run(struct asr_query *, + void (*)(struct asr_result *, void *), void *); diff --git a/openbsd-compat/libasr/asr_compat.c b/openbsd-compat/libasr/asr_compat.c new file mode 100644 index 00000000..ee958357 --- /dev/null +++ b/openbsd-compat/libasr/asr_compat.c @@ -0,0 +1,102 @@ +/* $OpenBSD: asr_debug.c,v 1.25 2018/04/28 15:16:49 schwarze 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 <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif + +#include "asr_compat.h" + +#ifndef HAVE___P_CLASS +const char * +__p_class(int c) +{ + switch(c) { + case C_IN: return "IN"; + case C_CHAOS: return "CHAOS"; + case C_HS: return "HESIOD"; + case C_ANY: return "ANY"; + default: return "?"; + } +}; +#endif /* !HAVE___P_CLASS */ + +#ifndef HAVE___P_TYPE +const char * +__p_type(int t) +{ + switch(t) { + case T_A: return "A"; + case T_NS: return "NS"; + case T_MD: return "MD"; + case T_MF: return "MF"; + case T_CNAME: return "CNAME"; + case T_SOA: return "SOA"; + case T_MB: return "MB"; + case T_MG: return "MG"; + case T_MR: return "MR"; + case T_NULL: return "NULL"; + case T_WKS: return "WKS"; + case T_PTR: return "PTR"; + case T_HINFO: return "HINFO"; + case T_MINFO: return "MINFO"; + case T_MX: return "MX"; + case T_TXT: return "TXT"; + case T_RP: return "RP"; + case T_AFSDB: return "AFSDB"; + case T_X25: return "X25"; + case T_ISDN: return "ISDN"; + case T_RT: return "RT"; + case T_NSAP: return "NSAP"; + case T_NSAP_PTR:return"NSAP_PTR"; + case T_SIG: return "SIG"; + case T_KEY: return "KEY"; + case T_PX: return "PX"; + case T_GPOS: return "GPOS"; + case T_AAAA: return "AAAA"; + case T_LOC: return "LOC"; + case T_NXT: return "NXT"; + case T_EID: return "EID"; + case T_NIMLOC: return "NIMLOC"; + case T_SRV: return "SRV"; + case T_ATMA: return "ATMA"; + case T_OPT: return "OPT"; + case T_IXFR: return "IXFR"; + case T_AXFR: return "AXFR"; + case T_MAILB: return "MAILB"; + case T_MAILA: return "MAILA"; +#ifdef T_UINFO + case T_UINFO: return "UINFO"; +#endif +#ifdef T_UID + case T_UID: return "UID"; +#endif +#ifdef T_GID + case T_GID: return "GID"; +#endif + case T_NAPTR: return "NAPTR"; +#ifdef T_UNSPEC + case T_UNSPEC: return "UNSPEC"; +#endif + case T_ANY: return "ANY"; + default: return "?"; + } +} +#endif /* !HAVE___P_TYPE */ diff --git a/openbsd-compat/libasr/asr_compat.h b/openbsd-compat/libasr/asr_compat.h new file mode 100644 index 00000000..2c7686a4 --- /dev/null +++ b/openbsd-compat/libasr/asr_compat.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 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. + */ + + +/* source compat */ +#define ASR_BUFSIZ 1024 + +#define DEF_WEAK(x) +#define __THREAD_NAME(x) __thread_name_ ## x + +#ifndef __BEGIN_HIDDEN_DECLS +#define __BEGIN_HIDDEN_DECLS +#endif +#ifndef __END_HIDDEN_DECLS +#define __END_HIDDEN_DECLS +#endif + +/* + * netdb.h + */ +#ifndef NETDB_SUCCESS +#define NETDB_SUCCESS 0 +#endif + +#ifndef NETDB_INTERNAL +#define NETDB_INTERNAL -1 +#endif + +#ifndef AI_FQDN +#define AI_FQDN AI_CANONNAME +#endif + +#ifndef AI_MASK +#define AI_MASK \ + (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_NUMERICSERV | AI_ADDRCONFIG | AI_FQDN) +#endif + +#ifndef SCOPE_DELIMITER +#define SCOPE_DELIMITER '%' +#endif + +#ifndef _PATH_HOSTS +#define _PATH_HOSTS "/etc/hosts" +#endif + +#ifndef _PATH_NETWORKS +#define _PATH_NETWORKS "/etc/networks" +#endif + +/* + * arpa/nameserv.h + */ +#ifndef T_OPT +#define T_OPT 41 +#endif + +#ifndef DNS_MESSAGEEXTFLAG_DO +#define DNS_MESSAGEEXTFLAG_DO 0x8000U +#endif + +#ifndef HAVE___P_CLASS +const char * __p_class(int); +#endif + +#ifndef HAVE___P_TYPE +const char * __p_type(int); +#endif diff --git a/openbsd-compat/libasr/asr_debug.c b/openbsd-compat/libasr/asr_debug.c new file mode 100644 index 00000000..be80436a --- /dev/null +++ b/openbsd-compat/libasr/asr_debug.c @@ -0,0 +1,362 @@ +/* $OpenBSD: asr_debug.c,v 1.26 2019/07/03 03:24:03 deraadt 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/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <arpa/inet.h> +#include <netdb.h> + +#include <asr.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" + +static const char *rcodetostr(uint16_t); +static const char *print_dname(const char *, char *, size_t); +static const char *print_header(const struct asr_dns_header *, char *, size_t); +static const char *print_query(const struct asr_dns_query *, char *, size_t); +static const char *print_rr(const struct asr_dns_rr *, char *, size_t); + +FILE *_asr_debug = NULL; + +#define OPCODE_SHIFT 11 + +static const char * +rcodetostr(uint16_t v) +{ + switch (v) { + case NOERROR: return "NOERROR"; + case FORMERR: return "FORMERR"; + case SERVFAIL: return "SERVFAIL"; + case NXDOMAIN: return "NXDOMAIN"; + case NOTIMP: return "NOTIMP"; + case REFUSED: return "REFUSED"; + default: return "?"; + } +} + +static const char * +print_dname(const char *_dname, char *buf, size_t max) +{ + return (_asr_strdname(_dname, buf, max)); +} + +static const char * +print_rr(const struct asr_dns_rr *rr, char *buf, size_t max) +{ + char *res; + char tmp[256]; + char tmp2[256]; + int r; + + res = buf; + + r = snprintf(buf, max, "%s %u %s %s ", + print_dname(rr->rr_dname, tmp, sizeof tmp), + rr->rr_ttl, + __p_class(rr->rr_class), + __p_type(rr->rr_type)); + if (r < 0 || (size_t)r >= max) { + buf[0] = '\0'; + return (buf); + } + + if ((size_t)r >= max) + return (buf); + + max -= r; + buf += r; + + switch (rr->rr_type) { + case T_CNAME: + print_dname(rr->rr.cname.cname, buf, max); + break; + case T_MX: + snprintf(buf, max, "%lu %s", + (unsigned long)rr->rr.mx.preference, + print_dname(rr->rr.mx.exchange, tmp, sizeof tmp)); + break; + case T_NS: + print_dname(rr->rr.ns.nsname, buf, max); + break; + case T_PTR: + print_dname(rr->rr.ptr.ptrname, buf, max); + break; + case T_SOA: + snprintf(buf, max, "%s %s %lu %lu %lu %lu %lu", + print_dname(rr->rr.soa.rname, tmp, sizeof tmp), + print_dname(rr->rr.soa.mname, tmp2, sizeof tmp2), + (unsigned long)rr->rr.soa.serial, + (unsigned long)rr->rr.soa.refresh, + (unsigned long)rr->rr.soa.retry, + (unsigned long)rr->rr.soa.expire, + (unsigned long)rr->rr.soa.minimum); + break; + case T_A: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntop(AF_INET, + &rr->rr.in_a.addr, tmp, sizeof tmp)); + break; + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntop(AF_INET6, + &rr->rr.in_aaaa.addr6, tmp, sizeof tmp)); + break; + default: + other: + snprintf(buf, max, "(rdlen=%i)", (int)rr->rr.other.rdlen); + break; + } + + return (res); +} + +static const char * +print_query(const struct asr_dns_query *q, char *buf, size_t max) +{ + char b[256]; + + snprintf(buf, max, "%s %s %s", + print_dname(q->q_dname, b, sizeof b), + __p_class(q->q_class), __p_type(q->q_type)); + + return (buf); +} + +static const char * +print_header(const struct asr_dns_header *h, char *buf, size_t max) +{ + snprintf(buf, max, + "id:0x%04x %s op:%i %s %s %s %s z:%i %s %s r:%s qd:%i an:%i ns:%i ar:%i", + ((int)h->id), + (h->flags & QR_MASK) ? "QR":" ", + (int)(OPCODE(h->flags) >> OPCODE_SHIFT), + (h->flags & AA_MASK) ? "AA":" ", + (h->flags & TC_MASK) ? "TC":" ", + (h->flags & RD_MASK) ? "RD":" ", + (h->flags & RA_MASK) ? "RA":" ", + (h->flags & Z_MASK), + (h->flags & AD_MASK) ? "AD":" ", + (h->flags & CD_MASK) ? "CD":" ", + rcodetostr(RCODE(h->flags)), + h->qdcount, h->ancount, h->nscount, h->arcount); + + return (buf); +} + +void +_asr_dump_packet(FILE *f, const void *data, size_t len) +{ + char buf[1024]; + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int i, an, ns, ar, n; + + if (f == NULL) + return; + + _asr_unpack_init(&p, data, len); + + if (_asr_unpack_header(&p, &h) == -1) { + fprintf(f, ";; BAD PACKET: %s\n", strerror(p.err)); + return; + } + + fprintf(f, ";; HEADER %s\n", print_header(&h, buf, sizeof buf)); + + if (h.qdcount) + fprintf(f, ";; QUERY SECTION:\n"); + for (i = 0; i < h.qdcount; i++) { + if (_asr_unpack_query(&p, &q) == -1) + goto error; + fprintf(f, "%s\n", print_query(&q, buf, sizeof buf)); + } + + an = 0; + ns = an + h.ancount; + ar = ns + h.nscount; + n = ar + h.arcount; + + for (i = 0; i < n; i++) { + if (i == an) + fprintf(f, "\n;; ANSWER SECTION:\n"); + if (i == ns) + fprintf(f, "\n;; AUTHORITY SECTION:\n"); + if (i == ar) + fprintf(f, "\n;; ADDITIONAL SECTION:\n"); + + if (_asr_unpack_rr(&p, &rr) == -1) + goto error; + fprintf(f, "%s\n", print_rr(&rr, buf, sizeof buf)); + } + + if (p.offset != len) + fprintf(f, ";; REMAINING GARBAGE %zu\n", len - p.offset); + + error: + if (p.err) + fprintf(f, ";; ERROR AT OFFSET %zu/%zu: %s\n", p.offset, p.len, + strerror(p.err)); +} + +const char * +_asr_print_sockaddr(const struct sockaddr *sa, char *buf, size_t len) +{ + char h[256]; + int portno; + union { + const struct sockaddr *sa; + const struct sockaddr_in *sin; + const struct sockaddr_in6 *sin6; + } s; + + s.sa = sa; + + switch (sa->sa_family) { + case AF_INET: + inet_ntop(AF_INET, &s.sin->sin_addr, h, sizeof h); + portno = ntohs(s.sin->sin_port); + break; + case AF_INET6: + inet_ntop(AF_INET6, &s.sin6->sin6_addr, h, sizeof h); + portno = ntohs(s.sin6->sin6_port); + break; + default: + snprintf(buf, len, "?"); + return (buf); + } + + snprintf(buf, len, "%s:%i", h, portno); + return (buf); +} + +void +_asr_dump_config(FILE *f, struct asr *a) +{ + char buf[256]; + int i; + struct asr_ctx *ac; + unsigned int o; + + if (f == NULL) + return; + + ac = a->a_ctx; + + fprintf(f, "--------- ASR CONFIG ---------------\n"); + fprintf(f, "DOMAIN \"%s\"\n", ac->ac_domain); + fprintf(f, "SEARCH\n"); + for (i = 0; i < ac->ac_domcount; i++) + fprintf(f, " \"%s\"\n", ac->ac_dom[i]); + fprintf(f, "OPTIONS\n"); + fprintf(f, " options:"); + o = ac->ac_options; + +#define PRINTOPT(flag, n) if (o & (flag)) { fprintf(f, " " n); o &= ~(flag); } + PRINTOPT(RES_INIT, "INIT"); + PRINTOPT(RES_DEBUG, "DEBUG"); + PRINTOPT(RES_USEVC, "USEVC"); + PRINTOPT(RES_IGNTC, "IGNTC"); + PRINTOPT(RES_RECURSE, "RECURSE"); + PRINTOPT(RES_DEFNAMES, "DEFNAMES"); + PRINTOPT(RES_STAYOPEN, "STAYOPEN"); + PRINTOPT(RES_DNSRCH, "DNSRCH"); + PRINTOPT(RES_NOALIASES, "NOALIASES"); + PRINTOPT(RES_USE_EDNS0, "USE_EDNS0"); + PRINTOPT(RES_USE_DNSSEC, "USE_DNSSEC"); + if (o) + fprintf(f, " 0x%08x", o); + fprintf(f, "\n"); + + fprintf(f, " ndots: %i\n", ac->ac_ndots); + fprintf(f, " family:"); + for (i = 0; ac->ac_family[i] != -1; i++) + fprintf(f, " %s", (ac->ac_family[i] == AF_INET)?"inet4":"inet6"); + fprintf(f, "\n"); + fprintf(f, "NAMESERVERS timeout=%i retry=%i\n", + ac->ac_nstimeout, + ac->ac_nsretries); + for (i = 0; i < ac->ac_nscount; i++) + fprintf(f, " %s\n", _asr_print_sockaddr(ac->ac_ns[i], buf, + sizeof buf)); + fprintf(f, "LOOKUP %s", ac->ac_db); + fprintf(f, "\n------------------------------------\n"); +} + +#define CASE(n) case n: return #n + +const char * +_asr_statestr(int state) +{ + switch (state) { + CASE(ASR_STATE_INIT); + CASE(ASR_STATE_NEXT_DOMAIN); + CASE(ASR_STATE_NEXT_DB); + CASE(ASR_STATE_SAME_DB); + CASE(ASR_STATE_NEXT_FAMILY); + CASE(ASR_STATE_NEXT_NS); + CASE(ASR_STATE_UDP_SEND); + CASE(ASR_STATE_UDP_RECV); + CASE(ASR_STATE_TCP_WRITE); + CASE(ASR_STATE_TCP_READ); + CASE(ASR_STATE_PACKET); + CASE(ASR_STATE_SUBQUERY); + CASE(ASR_STATE_NOT_FOUND); + CASE(ASR_STATE_HALT); + default: + return "?"; + } +}; + +const char * +_asr_querystr(int type) +{ + switch (type) { + CASE(ASR_SEND); + CASE(ASR_SEARCH); + CASE(ASR_GETRRSETBYNAME); + CASE(ASR_GETHOSTBYNAME); + CASE(ASR_GETHOSTBYADDR); + CASE(ASR_GETADDRINFO); + CASE(ASR_GETNAMEINFO); + default: + return "?"; + } +} + +const char * +_asr_transitionstr(int type) +{ + switch (type) { + CASE(ASYNC_COND); + CASE(ASYNC_DONE); + default: + return "?"; + } +} diff --git a/openbsd-compat/libasr/asr_private.h b/openbsd-compat/libasr/asr_private.h new file mode 100644 index 00000000..acf0e874 --- /dev/null +++ b/openbsd-compat/libasr/asr_private.h @@ -0,0 +1,359 @@ +/* $OpenBSD: asr_private.h,v 1.47 2018/04/28 15:16:49 schwarze 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 <stdio.h> + +#include "asr_compat.h" + +#define QR_MASK (0x1 << 15) +#define OPCODE_MASK (0xf << 11) +#define AA_MASK (0x1 << 10) +#define TC_MASK (0x1 << 9) +#define RD_MASK (0x1 << 8) +#define RA_MASK (0x1 << 7) +#define Z_MASK (0x1 << 6) +#define AD_MASK (0x1 << 5) +#define CD_MASK (0x1 << 4) +#define RCODE_MASK (0xf) + +#define OPCODE(v) ((v) & OPCODE_MASK) +#define RCODE(v) ((v) & RCODE_MASK) + + +struct asr_pack { + char *buf; + size_t len; + size_t offset; + int err; +}; + +struct asr_unpack { + const char *buf; + size_t len; + size_t offset; + int err; +}; + +struct asr_dns_header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +struct asr_dns_query { + char q_dname[MAXDNAME]; + uint16_t q_type; + uint16_t q_class; +}; + +struct asr_dns_rr { + char rr_dname[MAXDNAME]; + uint16_t rr_type; + uint16_t rr_class; + uint32_t rr_ttl; + union { + struct { + char cname[MAXDNAME]; + } cname; + struct { + uint16_t preference; + char exchange[MAXDNAME]; + } mx; + struct { + char nsname[MAXDNAME]; + } ns; + struct { + char ptrname[MAXDNAME]; + } ptr; + struct { + char mname[MAXDNAME]; + char rname[MAXDNAME]; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + struct { + struct in_addr addr; + } in_a; + struct { + struct in6_addr addr6; + } in_aaaa; + struct { + uint16_t rdlen; + const void *rdata; + } other; + } rr; +}; + + +#define ASR_MAXNS 5 +#define ASR_MAXDB 3 +#define ASR_MAXDOM 10 + +enum async_type { + ASR_SEND, + ASR_SEARCH, + ASR_GETRRSETBYNAME, + ASR_GETHOSTBYNAME, + ASR_GETHOSTBYADDR, + ASR_GETADDRINFO, + ASR_GETNAMEINFO, +}; + +#define ASR_DB_FILE 'f' +#define ASR_DB_DNS 'b' + +struct asr_ctx { + int ac_refcount; + int ac_options; + int ac_ndots; + char *ac_domain; + int ac_domcount; + char *ac_dom[ASR_MAXDOM]; + int ac_dbcount; + char ac_db[ASR_MAXDB + 1]; + int ac_family[3]; + + int ac_nscount; + int ac_nstimeout; + int ac_nsretries; + struct sockaddr *ac_ns[ASR_MAXNS]; + +}; + +struct asr { + pid_t a_pid; + time_t a_mtime; + time_t a_rtime; + struct asr_ctx *a_ctx; +}; + +#define ASYNC_COND 0 +#define ASYNC_DONE 1 + +#define ASYNC_DOM_FQDN 0x00000001 +#define ASYNC_DOM_NDOTS 0x00000002 +#define ASYNC_DOM_DOMAIN 0x00000004 +#define ASYNC_DOM_ASIS 0x00000008 + +#define ASYNC_NODATA 0x00000100 +#define ASYNC_AGAIN 0x00000200 + +#define ASYNC_GETNET 0x00001000 +#define ASYNC_EXTOBUF 0x00002000 + +#define ASYNC_NO_INET 0x00010000 +#define ASYNC_NO_INET6 0x00020000 + +struct asr_query { + int (*as_run)(struct asr_query *, struct asr_result *); + struct asr_ctx *as_ctx; + int as_type; + int as_flags; + int as_state; + + /* cond */ + int as_timeout; + int as_fd; + struct asr_query *as_subq; + + /* loop indices in ctx */ + int as_dom_step; + int as_dom_idx; + int as_dom_flags; + int as_family_idx; + int as_db_idx; + + int as_count; + + union { + struct { + uint16_t reqid; + int class; + int type; + char *dname; /* not fqdn! */ + int rcode; /* response code */ + int ancount; /* answer count */ + + int nsidx; + int nsloop; + + /* io buffers for query/response */ + unsigned char *obuf; + size_t obuflen; + size_t obufsize; + unsigned char *ibuf; + size_t ibuflen; + size_t ibufsize; + size_t datalen; /* for tcp io */ + uint16_t pktlen; + } dns; + + struct { + int class; + int type; + char *name; + int saved_h_errno; + } search; + + struct { + int flags; + int class; + int type; + char *name; + } rrset; + + struct { + char *name; + int family; + char addr[16]; + int addrlen; + int subq_h_errno; + } hostnamadr; + + struct { + char *hostname; + char *servname; + int port_tcp; + int port_udp; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + + struct addrinfo hints; + char *fqdn; + struct addrinfo *aifirst; + struct addrinfo *ailast; + } ai; + + struct { + char *hostname; + char *servname; + size_t hostnamelen; + size_t servnamelen; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + int flags; + } ni; +#define MAXTOKEN 10 + } as; + +}; + +#define AS_DB(p) ((p)->as_ctx->ac_db[(p)->as_db_idx - 1]) +#define AS_FAMILY(p) ((p)->as_ctx->ac_family[(p)->as_family_idx]) + +enum asr_state { + ASR_STATE_INIT, + ASR_STATE_NEXT_DOMAIN, + ASR_STATE_NEXT_DB, + ASR_STATE_SAME_DB, + ASR_STATE_NEXT_FAMILY, + ASR_STATE_NEXT_NS, + ASR_STATE_UDP_SEND, + ASR_STATE_UDP_RECV, + ASR_STATE_TCP_WRITE, + ASR_STATE_TCP_READ, + ASR_STATE_PACKET, + ASR_STATE_SUBQUERY, + ASR_STATE_NOT_FOUND, + ASR_STATE_HALT, +}; + +#define MAXPACKETSZ 4096 + +__BEGIN_HIDDEN_DECLS + +/* asr_utils.c */ +void _asr_pack_init(struct asr_pack *, char *, size_t); +int _asr_pack_header(struct asr_pack *, const struct asr_dns_header *); +int _asr_pack_query(struct asr_pack *, uint16_t, uint16_t, const char *); +int _asr_pack_edns0(struct asr_pack *, uint16_t, int); +void _asr_unpack_init(struct asr_unpack *, const char *, size_t); +int _asr_unpack_header(struct asr_unpack *, struct asr_dns_header *); +int _asr_unpack_query(struct asr_unpack *, struct asr_dns_query *); +int _asr_unpack_rr(struct asr_unpack *, struct asr_dns_rr *); +int _asr_sockaddr_from_str(struct sockaddr *, int, const char *); +ssize_t _asr_dname_from_fqdn(const char *, char *, size_t); +ssize_t _asr_addr_as_fqdn(const char *, int, char *, size_t); + +/* asr.c */ +void _asr_resolver_done(void *); +struct asr_ctx *_asr_use_resolver(void *); +struct asr_ctx *_asr_no_resolver(void); +void _asr_ctx_unref(struct asr_ctx *); +struct asr_query *_asr_async_new(struct asr_ctx *, int); +void _asr_async_free(struct asr_query *); +size_t _asr_make_fqdn(const char *, const char *, char *, size_t); +char *_asr_strdname(const char *, char *, size_t); +int _asr_iter_db(struct asr_query *); +int _asr_parse_namedb_line(FILE *, char **, int, char *, size_t); + +/* *_async.c */ +struct asr_query *_res_query_async_ctx(const char *, int, int, struct asr_ctx *); +struct asr_query *_res_search_async_ctx(const char *, int, int, struct asr_ctx *); +struct asr_query *_gethostbyaddr_async_ctx(const void *, socklen_t, int, + struct asr_ctx *); + +int _asr_iter_domain(struct asr_query *, const char *, char *, size_t); + +#ifdef DEBUG + +#define DPRINT(...) do { if(_asr_debug) { \ + fprintf(_asr_debug, __VA_ARGS__); \ + } } while (0) +#define DPRINT_PACKET(n, p, s) do { if(_asr_debug) { \ + fprintf(_asr_debug, "----- %s -----\n", n); \ + _asr_dump_packet(_asr_debug, (p), (s)); \ + fprintf(_asr_debug, "--------------\n"); \ + } } while (0) + +#else /* DEBUG */ + +#define DPRINT(...) +#define DPRINT_PACKET(...) + +#endif /* DEBUG */ + +const char *_asr_querystr(int); +const char *_asr_statestr(int); +const char *_asr_transitionstr(int); +const char *_asr_print_sockaddr(const struct sockaddr *, char *, size_t); +void _asr_dump_config(FILE *, struct asr *); +void _asr_dump_packet(FILE *, const void *, size_t); + +extern FILE *_asr_debug; + +#define async_set_state(a, s) do { \ + DPRINT("asr: [%s@%p] %s -> %s\n", \ + _asr_querystr((a)->as_type), \ + as, \ + _asr_statestr((a)->as_state), \ + _asr_statestr((s))); \ + (a)->as_state = (s); } while (0) + +__END_HIDDEN_DECLS diff --git a/openbsd-compat/libasr/asr_run.3 b/openbsd-compat/libasr/asr_run.3 new file mode 100644 index 00000000..61c1b02c --- /dev/null +++ b/openbsd-compat/libasr/asr_run.3 @@ -0,0 +1,316 @@ +.\" $OpenBSD: asr_run.3,v 1.3 2017/02/18 19:23:05 jca Exp $ +.\" +.\" Copyright (c) 2012-2014, 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. +.\" +.Dd $Mdocdate: February 18 2017 $ +.Dt ASR_RUN 3 +.Os +.Sh NAME +.Nm asr_run , +.Nm asr_run_sync , +.Nm asr_abort , +.Nm res_send_async , +.Nm res_query_async , +.Nm res_search_async , +.Nm getrrsetbyname_async , +.Nm gethostbyname_async , +.Nm gethostbyname2_async , +.Nm gethostbyaddr_async , +.Nm getnetbyname_async , +.Nm getnetbyaddr_async , +.Nm getaddrinfo_async , +.Nm getnameinfo_async +.Nd asynchronous resolver functions +.Sh SYNOPSIS +.In sys/types.h +.In sys/socket.h +.In netdb.h +.In asr.h +.Ft int +.Fn asr_run "struct asr_query *aq" "struct asr_result *ar" +.Ft int +.Fn asr_run_sync "struct asr_query *aq" "struct asr_result *ar" +.Ft void +.Fn asr_abort "struct asr_query *aq" +.Ft struct asr_query * +.Fn res_send_async "const unsigned char *pkt" "int pktlen" "void *asr" +.Ft struct asr_query * +.Fn res_query_async "const char *name" "int class" "int type" "void *asr" +.Ft struct asr_query * +.Fn res_search_async "const char *name" "int class" "int type" "void *asr" +.Ft struct asr_query * +.Fn getrrsetbyname_async "const char *hostname" "unsigned int rdclass" "unsigned int rdtype" "unsigned int flags" "void *asr" +.Ft struct asr_query * +.Fn gethostbyname_async "const char *name" "void *asr" +.Ft struct asr_query * +.Fn gethostbyname2_async "const char *name" "int af" "void *asr" +.Ft struct asr_query * +.Fn gethostbyaddr_async "const void *addr" "socklen_t len" "int af" "void *asr" +.Ft struct asr_query * +.Fn getnetbyname_async "const char *name" "void *asr" +.Ft struct asr_query * +.Fn getnetbyaddr_async "in_addr_t net" "int type" "void *asr" +.Ft struct asr_query * +.Fn getaddrinfo_async "const char *hostname" "const char *servname" "const struct addrinfo *hints" "void *asr" +.Ft struct asr_query * +.Fn getnameinfo_async "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags" "void *asr" +.Sh DESCRIPTION +The +.Nm asr +functions provide a simple interface for asynchronous address +resolution and nameserver querying. +They should be used in place of the classical resolver functions +of libc when blocking is not desirable. +.Pp +The principle of operation is as follows: +All async requests are made against an +.Nm asr +context which basically defines a list of sources to query and a +strategy to do so. +The user creates a query through one of the dedicated functions, and +gets a handle representing the internal query. +A query is a state-machine that can be run to try to fulfill a +particular request. +This is done by calling in a generic API that performs the state +transitions until it needs to give the control back to the user, +either because a result is available, or because the next transition +implies a blocking call (a file descriptor needs to be read from or +written to). +The user is responsible for dealing with the situation: either get +the result, or wait until the fd conditions are met, and then call +back into the resolving machinery when it is ready to proceed. +.Pp +The +.Fn asr_run +function drives the resolving process. +It runs the asynchronous query represented by the +.Fa aq +handle until a result is available, or until it cannot continue +without blocking. +The results are returned to the user through the +.Fa ar +parameter, which must be a valid pointer to user allocated memory. +.Fa ar +is defined as: +.Bd -literal +struct asr_result { + + /* Fields set if the query is not done yet (asr_run returns 0) */ + int ar_cond; /* ASR_WANT_READ or ASR_WANT_WRITE */ + int ar_fd; /* the fd waiting for io condition */ + int ar_timeout; /* time to wait for in milliseconds */ + + /* Error fields. Depends on the query type. */ + int ar_errno; + int ar_h_errno; + int ar_gai_errno; + int ar_rrset_errno; + + /* Result for res_*_async() calls */ + int ar_count; /* number of answers in the dns reply */ + int ar_rcode; /* response code in the dns reply */ + void *ar_data; /* raw reply packet (must be freed) */ + int ar_datalen; /* reply packet length */ + struct sockaddr_storage ar_ns; /* nameserver that responded */ + + /* Result for other calls. Must be freed properly. */ + struct addrinfo *ar_addrinfo; + struct rrsetinfo *ar_rrsetinfo; + struct hostent *ar_hostent; + struct netent *ar_netent; +}; +.Ed +.Pp +The function returns one of the following values: +.Bl -tag -width "0 " -offset indent +.It 0 +The query cannot be processed further until a specific condition on a +file descriptor becomes true. +The following members of the +.Fa ar +structure are filled: +.Pp +.Bl -tag -width "ar_timeout " -compact +.It Fa ar_cond +one of ASR_WANT_READ or ASR_WANT_WRITE, +.It Fa ar_fd +the file descriptor waiting for an IO operation, +.It Fa ar_timeout +the amount of time to wait for in milliseconds. +.El +.Pp +The caller is expected to call +.Fn asr_run +again once the condition holds or the timeout expires. +.It 1 +The query is completed. +The members relevant to the actual async query type are set accordingly, +including error conditions. +In any case, the query is cleared and its handle is invalidated. +.El +.Pp +Note that although the query itself may fail (the error being properly reported +in the +.Fa ar +structure), the +.Fn asr_run +function itself cannot fail and it always preserves errno. +.Pp +The +.Fn asr_run_sync +function is a wrapper around +.Fn asr_run +that handles the read/write conditions, thus falling back to a blocking +interface. +It only returns 1. +It also preserves errno. +.Pp +The +.Fn asr_abort +function clears a running query. +It can be called when the query is waiting on a file descriptor. +Note that a completed query is already cleared when +.Fn asr_run +returns, so +.Fn asr_abort +must not be called in this case. +.Pp +The remaining functions are used to initiate different kinds of query +on the +.Fa asr +resolver context. +The specific operational details for each of them are described below. +All functions return a handle to an internal query, or NULL if they could +not allocate the necessary resources to initiate the query. +All other errors (especially invalid parameters) are reported when calling +.Fn asr_run . +They usually have the same interface as an existing resolver function, with +an additional +.Ar asr +argument, which specifies the context to use for this request. +For now, the argument must always be NULL, which will use the default +context for the current thread. +.Pp +The +.Fn res_send_async , +.Fn res_query_async +and +.Fn res_search_async +functions are asynchronous versions of the standard libc resolver routines. +Their interface is very similar, except that the response buffer is always +allocated internally. +The return value is found upon completion in the +.Fa ar_datalen +member of the response structure. +In addition, the +.Fa ar_ns +structure contains the address of the DNS server that sent the response, +.Fa ar_rcode +contains the code returned by the server in the DNS response packet, and +.Fa ar_count +contains the number of answers in the packet. +If a response is received it is placed in a newly allocated buffer +and returned as +.Fa ar_data +member. +This buffer must be freed by the caller. +On error, the +.Fa ar_errno +and +.Fa ar_h_errno +members are set accordingly. +.Pp +The +.Fn getrrsetbyname_async +function is an asynchronous version of +.Xr getrrsetbyname 3 . +Upon completion, the return code is found in +.Fa ar_rrset_errno +and the address to the newly allocated result set is set in +.Fa ar_rrsetinfo . +As for the blocking function, it must be freed by calling +.Xr freerrset 3 . +.Pp +The +.Fn gethostbyname_async , +.Fn gethostbyname2_async +and +.Fn gethostbyaddr_async +functions provide an asynchronous version of the network host entry functions. +Upon completion, +.Ar ar_h_errno +is set and the resulting hostent address, if found, is set +in the +.Ar ar_hostent +field. +Note that unlike their blocking counterparts, these functions always return a +pointer to newly allocated memory, which must be released by the caller using +.Xr free 3 . +.Pp +Similarly, the +.Fn getnetbyname_async +and +.Fn getnetbyaddr_async +functions provide an asynchronous version of the network entry functions. +Upon completion, +.Ar ar_h_errno +is set and the resulting netent address, if found, is set +in the +.Ar ar_netent +field. +The memory there is also allocated for the request, and it must be freed by +.Xr free 3 . +.Pp +The +.Fn getaddrinfo_async +function is an asynchronous version of the +.Xr getaddrinfo 3 +call. +It provides a chain of addrinfo structures with all valid combinations of +socket address for the given +.Fa hostname , +.Fa servname +and +.Fa hints . +Those three parameters have the same meaning as for the blocking counterpart. +Upon completion the return code is set in +.Fa ar_gai_errno . +The +.Fa ar_errno +member may also be set. +On success, the +.Fa ar_addrinfo +member points to a newly allocated list of addrinfo. +This list must be freed with +.Xr freeaddrinfo 3 . +.Sh WORKING WITH THREADS +This implementation of the asynchronous resolver interface is thread-safe +and lock-free internally, but the following restriction applies: +Two different threads must not create queries on the same context or +run queries originating from the same context at the same time. +If they want to do that, all calls must be protected by a mutex around +that context. +.Pp +It is generally not a problem since the main point of the asynchronous +resolver is to multiplex queries within a single thread of control, +so sharing a resolver among threads is not useful. +.Sh SEE ALSO +.Xr getaddrinfo 3 , +.Xr gethostbyname 3 , +.Xr getnameinfo 3 , +.Xr getnetbyname 3 , +.Xr getrrsetbyname 3 , +.Xr res_send 3 , +.Xr resolv.conf 5 diff --git a/openbsd-compat/libasr/asr_utils.c b/openbsd-compat/libasr/asr_utils.c new file mode 100644 index 00000000..e3d24c93 --- /dev/null +++ b/openbsd-compat/libasr/asr_utils.c @@ -0,0 +1,574 @@ +/* $OpenBSD: asr_utils.c,v 1.18 2017/09/23 20:55:06 jca Exp $ */ +/* + * Copyright (c) 2009-2012 Eric Faurot <eric@faurot.net> + * + * 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> +#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 <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int dname_check_label(const char *, size_t); +static ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *, + char *, size_t); + +static int unpack_data(struct asr_unpack *, void *, size_t); +static int unpack_u16(struct asr_unpack *, uint16_t *); +static int unpack_u32(struct asr_unpack *, uint32_t *); +static int unpack_inaddr(struct asr_unpack *, struct in_addr *); +static int unpack_in6addr(struct asr_unpack *, struct in6_addr *); +static int unpack_dname(struct asr_unpack *, char *, size_t); + +static int pack_data(struct asr_pack *, const void *, size_t); +static int pack_u16(struct asr_pack *, uint16_t); +static int pack_dname(struct asr_pack *, const char *); + +static int +dname_check_label(const char *s, size_t l) +{ + if (l == 0 || l > 63) + return (-1); + + return (0); +} + +ssize_t +_asr_dname_from_fqdn(const char *str, char *dst, size_t max) +{ + ssize_t res; + size_t l, n; + char *d; + + res = 0; + + /* special case: the root domain */ + if (str[0] == '.') { + if (str[1] != '\0') + return (-1); + if (dst && max >= 1) + *dst = '\0'; + return (1); + } + + for (; *str; str = d + 1) { + + d = strchr(str, '.'); + if (d == NULL || d == str) + return (-1); + + l = (d - str); + + if (dname_check_label(str, l) == -1) + return (-1); + + res += l + 1; + + if (dst) { + *dst++ = l; + max -= 1; + n = (l > max) ? max : l; + memmove(dst, str, n); + max -= n; + if (max == 0) + dst = NULL; + else + dst += n; + } + } + + if (dst) + *dst++ = '\0'; + + return (res + 1); +} + +static ssize_t +dname_expand(const unsigned char *data, size_t len, size_t offset, + size_t *newoffset, char *dst, size_t max) +{ + size_t n, count, end, ptr, start; + ssize_t res; + + if (offset >= len) + return (-1); + + res = 0; + end = start = offset; + + for (; (n = data[offset]); ) { + if ((n & 0xc0) == 0xc0) { + if (offset + 2 > len) + return (-1); + ptr = 256 * (n & ~0xc0) + data[offset + 1]; + if (ptr >= start) + return (-1); + if (end < offset + 2) + end = offset + 2; + offset = start = ptr; + continue; + } + if (offset + n + 1 > len) + return (-1); + + if (dname_check_label(data + offset + 1, n) == -1) + return (-1); + + /* copy n + at offset+1 */ + if (dst != NULL && max != 0) { + count = (max < n + 1) ? (max) : (n + 1); + memmove(dst, data + offset, count); + dst += count; + max -= count; + } + res += n + 1; + offset += n + 1; + if (end < offset) + end = offset; + } + if (end < offset + 1) + end = offset + 1; + + if (dst != NULL && max != 0) + dst[0] = 0; + if (newoffset) + *newoffset = end; + return (res + 1); +} + +void +_asr_pack_init(struct asr_pack *pack, char *buf, size_t len) +{ + pack->buf = buf; + pack->len = len; + pack->offset = 0; + pack->err = 0; +} + +void +_asr_unpack_init(struct asr_unpack *unpack, const char *buf, size_t len) +{ + unpack->buf = buf; + unpack->len = len; + unpack->offset = 0; + unpack->err = 0; +} + +static int +unpack_data(struct asr_unpack *p, void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len - p->offset < len) { + p->err = EOVERFLOW; + return (-1); + } + + memmove(data, p->buf + p->offset, len); + p->offset += len; + + return (0); +} + +static int +unpack_u16(struct asr_unpack *p, uint16_t *u16) +{ + if (unpack_data(p, u16, 2) == -1) + return (-1); + + *u16 = ntohs(*u16); + + return (0); +} + +static int +unpack_u32(struct asr_unpack *p, uint32_t *u32) +{ + if (unpack_data(p, u32, 4) == -1) + return (-1); + + *u32 = ntohl(*u32); + + return (0); +} + +static int +unpack_inaddr(struct asr_unpack *p, struct in_addr *a) +{ + return (unpack_data(p, a, 4)); +} + +static int +unpack_in6addr(struct asr_unpack *p, struct in6_addr *a6) +{ + return (unpack_data(p, a6, 16)); +} + +static int +unpack_dname(struct asr_unpack *p, char *dst, size_t max) +{ + ssize_t e; + + if (p->err) + return (-1); + + e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max); + if (e == -1) { + p->err = EINVAL; + return (-1); + } + if (e < 0 || e > MAXDNAME) { + p->err = ERANGE; + return (-1); + } + + return (0); +} + +int +_asr_unpack_header(struct asr_unpack *p, struct asr_dns_header *h) +{ + if (unpack_data(p, h, HFIXEDSZ) == -1) + return (-1); + + h->flags = ntohs(h->flags); + h->qdcount = ntohs(h->qdcount); + h->ancount = ntohs(h->ancount); + h->nscount = ntohs(h->nscount); + h->arcount = ntohs(h->arcount); + + return (0); +} + +int +_asr_unpack_query(struct asr_unpack *p, struct asr_dns_query *q) +{ + unpack_dname(p, q->q_dname, sizeof(q->q_dname)); + unpack_u16(p, &q->q_type); + unpack_u16(p, &q->q_class); + + return (p->err) ? (-1) : (0); +} + +int +_asr_unpack_rr(struct asr_unpack *p, struct asr_dns_rr *rr) +{ + uint16_t rdlen; + size_t save_offset; + + unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname)); + unpack_u16(p, &rr->rr_type); + unpack_u16(p, &rr->rr_class); + unpack_u32(p, &rr->rr_ttl); + unpack_u16(p, &rdlen); + + if (p->err) + return (-1); + + if (p->len - p->offset < rdlen) { + p->err = EOVERFLOW; + return (-1); + } + + save_offset = p->offset; + + switch (rr->rr_type) { + + case T_CNAME: + unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname)); + break; + + case T_MX: + unpack_u16(p, &rr->rr.mx.preference); + unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange)); + break; + + case T_NS: + unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname)); + break; + + case T_PTR: + unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname)); + break; + + case T_SOA: + unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname)); + unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname)); + unpack_u32(p, &rr->rr.soa.serial); + unpack_u32(p, &rr->rr.soa.refresh); + unpack_u32(p, &rr->rr.soa.retry); + unpack_u32(p, &rr->rr.soa.expire); + unpack_u32(p, &rr->rr.soa.minimum); + break; + + case T_A: + if (rr->rr_class != C_IN) + goto other; + unpack_inaddr(p, &rr->rr.in_a.addr); + break; + + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + unpack_in6addr(p, &rr->rr.in_aaaa.addr6); + break; + default: + other: + rr->rr.other.rdata = p->buf + p->offset; + rr->rr.other.rdlen = rdlen; + p->offset += rdlen; + } + + if (p->err) + return (-1); + + /* make sure that the advertised rdlen is really ok */ + if (p->offset - save_offset != rdlen) + p->err = EINVAL; + + return (p->err) ? (-1) : (0); +} + +static int +pack_data(struct asr_pack *p, const void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len < p->offset + len) { + p->err = EOVERFLOW; + return (-1); + } + + memmove(p->buf + p->offset, data, len); + p->offset += len; + + return (0); +} + +static int +pack_u16(struct asr_pack *p, uint16_t v) +{ + v = htons(v); + + return (pack_data(p, &v, 2)); +} + +static int +pack_dname(struct asr_pack *p, const char *dname) +{ + /* dname compression would be nice to have here. + * need additionnal context. + */ + return (pack_data(p, dname, strlen(dname) + 1)); +} + +int +_asr_pack_header(struct asr_pack *p, const struct asr_dns_header *h) +{ + struct asr_dns_header c; + + c.id = h->id; + c.flags = htons(h->flags); + c.qdcount = htons(h->qdcount); + c.ancount = htons(h->ancount); + c.nscount = htons(h->nscount); + c.arcount = htons(h->arcount); + + return (pack_data(p, &c, HFIXEDSZ)); +} + +int +_asr_pack_query(struct asr_pack *p, uint16_t type, uint16_t class, const char *dname) +{ + pack_dname(p, dname); + pack_u16(p, type); + pack_u16(p, class); + + return (p->err) ? (-1) : (0); +} + +int +_asr_pack_edns0(struct asr_pack *p, uint16_t pktsz, int dnssec_do) +{ + DPRINT("asr EDNS0 pktsz:%hu dnssec:%s\n", pktsz, + dnssec_do ? "yes" : "no"); + + pack_dname(p, ""); /* root */ + pack_u16(p, T_OPT); /* OPT */ + pack_u16(p, pktsz); /* UDP payload size */ + + /* extended RCODE and flags */ + pack_u16(p, 0); + pack_u16(p, dnssec_do ? DNS_MESSAGEEXTFLAG_DO : 0); + + pack_u16(p, 0); /* RDATA len */ + + return (p->err) ? (-1) : (0); +} + +int +_asr_sockaddr_from_str(struct sockaddr *sa, int family, const char *str) +{ + struct in_addr ina; + struct in6_addr in6a; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + char *cp, *str2; + const char *errstr; + + switch (family) { + case PF_UNSPEC: + if (_asr_sockaddr_from_str(sa, PF_INET, str) == 0) + return (0); + return _asr_sockaddr_from_str(sa, PF_INET6, str); + + case PF_INET: + if (inet_pton(PF_INET, str, &ina) != 1) + return (-1); + + sin = (struct sockaddr_in *)sa; + memset(sin, 0, sizeof *sin); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin->sin_len = sizeof(struct sockaddr_in); +#endif + sin->sin_family = PF_INET; + sin->sin_addr.s_addr = ina.s_addr; + return (0); + + case PF_INET6: + cp = strchr(str, SCOPE_DELIMITER); + if (cp) { + str2 = strdup(str); + if (str2 == NULL) + return (-1); + str2[cp - str] = '\0'; + if (inet_pton(PF_INET6, str2, &in6a) != 1) { + free(str2); + return (-1); + } + cp++; + free(str2); + } else if (inet_pton(PF_INET6, str, &in6a) != 1) + return (-1); + + sin6 = (struct sockaddr_in6 *)sa; + memset(sin6, 0, sizeof *sin6); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_family = PF_INET6; + sin6->sin6_addr = in6a; + + if (cp == NULL) + return (0); + + if (IN6_IS_ADDR_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_NODELOCAL(&in6a)) + if ((sin6->sin6_scope_id = if_nametoindex(cp))) + return (0); + + sin6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); + if (errstr) + return (-1); + return (0); + + default: + break; + } + + return (-1); +} + +ssize_t +_asr_addr_as_fqdn(const char *addr, int family, char *dst, size_t max) +{ + const struct in6_addr *in6_addr; + in_addr_t in_addr; + + switch (family) { + case AF_INET: + in_addr = ntohl(*((const in_addr_t *)addr)); + snprintf(dst, max, + "%d.%d.%d.%d.in-addr.arpa.", + in_addr & 0xff, + (in_addr >> 8) & 0xff, + (in_addr >> 16) & 0xff, + (in_addr >> 24) & 0xff); + break; + case AF_INET6: + in6_addr = (const struct in6_addr *)addr; + snprintf(dst, max, + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "ip6.arpa.", + in6_addr->s6_addr[15] & 0xf, + (in6_addr->s6_addr[15] >> 4) & 0xf, + in6_addr->s6_addr[14] & 0xf, + (in6_addr->s6_addr[14] >> 4) & 0xf, + in6_addr->s6_addr[13] & 0xf, + (in6_addr->s6_addr[13] >> 4) & 0xf, + in6_addr->s6_addr[12] & 0xf, + (in6_addr->s6_addr[12] >> 4) & 0xf, + in6_addr->s6_addr[11] & 0xf, + (in6_addr->s6_addr[11] >> 4) & 0xf, + in6_addr->s6_addr[10] & 0xf, + (in6_addr->s6_addr[10] >> 4) & 0xf, + in6_addr->s6_addr[9] & 0xf, + (in6_addr->s6_addr[9] >> 4) & 0xf, + in6_addr->s6_addr[8] & 0xf, + (in6_addr->s6_addr[8] >> 4) & 0xf, + in6_addr->s6_addr[7] & 0xf, + (in6_addr->s6_addr[7] >> 4) & 0xf, + in6_addr->s6_addr[6] & 0xf, + (in6_addr->s6_addr[6] >> 4) & 0xf, + in6_addr->s6_addr[5] & 0xf, + (in6_addr->s6_addr[5] >> 4) & 0xf, + in6_addr->s6_addr[4] & 0xf, + (in6_addr->s6_addr[4] >> 4) & 0xf, + in6_addr->s6_addr[3] & 0xf, + (in6_addr->s6_addr[3] >> 4) & 0xf, + in6_addr->s6_addr[2] & 0xf, + (in6_addr->s6_addr[2] >> 4) & 0xf, + in6_addr->s6_addr[1] & 0xf, + (in6_addr->s6_addr[1] >> 4) & 0xf, + in6_addr->s6_addr[0] & 0xf, + (in6_addr->s6_addr[0] >> 4) & 0xf); + break; + default: + return (-1); + } + return (0); +} diff --git a/openbsd-compat/libasr/getaddrinfo.c b/openbsd-compat/libasr/getaddrinfo.c new file mode 100644 index 00000000..37fe2d4d --- /dev/null +++ b/openbsd-compat/libasr/getaddrinfo.c @@ -0,0 +1,55 @@ +/* $OpenBSD: getaddrinfo.c,v 1.9 2015/10/08 14:08:44 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> + +int +getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + struct asr_query *as; + struct asr_result ar; + int saved_errno = errno; + + if (hints == NULL || (hints->ai_flags & AI_NUMERICHOST) == 0) + res_init(); + + as = getaddrinfo_async(hostname, servname, hints, NULL); + if (as == NULL) { + if (errno == ENOMEM) { + errno = saved_errno; + return (EAI_MEMORY); + } + return (EAI_SYSTEM); + } + + asr_run_sync(as, &ar); + + *res = ar.ar_addrinfo; + if (ar.ar_gai_errno == EAI_SYSTEM) + errno = ar.ar_errno; + + return (ar.ar_gai_errno); +} +DEF_WEAK(getaddrinfo); diff --git a/openbsd-compat/libasr/getaddrinfo_async.c b/openbsd-compat/libasr/getaddrinfo_async.c new file mode 100644 index 00000000..1fd44ff7 --- /dev/null +++ b/openbsd-compat/libasr/getaddrinfo_async.c @@ -0,0 +1,756 @@ +/* $OpenBSD: getaddrinfo_async.c,v 1.56 2018/11/03 09:13:24 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 <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <net/if.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <ifaddrs.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +struct match { + int family; + int socktype; + int protocol; +}; + +static int getaddrinfo_async_run(struct asr_query *, struct asr_result *); +static int get_port(const char *, const char *, int); +static int iter_family(struct asr_query *, int); +static int addrinfo_add(struct asr_query *, const struct sockaddr *, const char *); +static int addrinfo_from_file(struct asr_query *, int, FILE *); +static int addrinfo_from_pkt(struct asr_query *, char *, size_t); +static int addrconfig_setup(struct asr_query *); + +static const struct match matches[] = { + { PF_INET, SOCK_DGRAM, IPPROTO_UDP }, + { PF_INET, SOCK_STREAM, IPPROTO_TCP }, + { PF_INET, SOCK_RAW, 0 }, + { PF_INET6, SOCK_DGRAM, IPPROTO_UDP }, + { PF_INET6, SOCK_STREAM, IPPROTO_TCP }, + { PF_INET6, SOCK_RAW, 0 }, + { -1, 0, 0, }, +}; + +#define MATCH_FAMILY(a, b) ((a) == matches[(b)].family || (a) == PF_UNSPEC) +#define MATCH_PROTO(a, b) ((a) == matches[(b)].protocol || (a) == 0 || matches[(b)].protocol == 0) +/* Do not match SOCK_RAW unless explicitly specified */ +#define MATCH_SOCKTYPE(a, b) ((a) == matches[(b)].socktype || ((a) == 0 && \ + matches[(b)].socktype != SOCK_RAW)) + +enum { + DOM_INIT, + DOM_DOMAIN, + DOM_DONE +}; + +struct asr_query * +getaddrinfo_async(const char *hostname, const char *servname, + const struct addrinfo *hints, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + if (hints == NULL || (hints->ai_flags & AI_NUMERICHOST) == 0) + ac = _asr_use_resolver(asr); + else + ac = _asr_no_resolver(); + if ((as = _asr_async_new(ac, ASR_GETADDRINFO)) == NULL) + goto abort; /* errno set */ + as->as_run = getaddrinfo_async_run; + + if (hostname) { + if ((as->as.ai.hostname = strdup(hostname)) == NULL) + goto abort; /* errno set */ + } + if (servname && (as->as.ai.servname = strdup(servname)) == NULL) + goto abort; /* errno set */ + if (hints) + memmove(&as->as.ai.hints, hints, sizeof *hints); + else { + memset(&as->as.ai.hints, 0, sizeof as->as.ai.hints); + as->as.ai.hints.ai_family = PF_UNSPEC; + as->as.ai.hints.ai_flags = AI_ADDRCONFIG; + } + + _asr_ctx_unref(ac); + return (as); + abort: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(getaddrinfo_async); + +static int +getaddrinfo_async_run(struct asr_query *as, struct asr_result *ar) +{ + char fqdn[MAXDNAME]; + const char *str; + struct addrinfo *ai; + int i, family, r; + FILE *f; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + /* + * First, make sure the parameters are valid. + */ + + as->as_count = 0; + + if (as->as.ai.hostname == NULL && + as->as.ai.servname == NULL) { + ar->ar_gai_errno = EAI_NONAME; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ai.hostname && as->as.ai.hostname[0] == '\0') { + ar->ar_gai_errno = EAI_NODATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + ai = &as->as.ai.hints; + +#ifdef EAI_BADHINTS + if (ai->ai_addrlen || + ai->ai_canonname || + ai->ai_addr || + ai->ai_next) { + ar->ar_gai_errno = EAI_BADHINTS; + async_set_state(as, ASR_STATE_HALT); + break; + } +#endif + + if (ai->ai_flags & ~AI_MASK || + (ai->ai_flags & AI_CANONNAME && ai->ai_flags & AI_FQDN)) { + ar->ar_gai_errno = EAI_BADFLAGS; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_family != PF_UNSPEC && + ai->ai_family != PF_INET && + ai->ai_family != PF_INET6) { + ar->ar_gai_errno = EAI_FAMILY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_socktype && + ai->ai_socktype != SOCK_DGRAM && + ai->ai_socktype != SOCK_STREAM && + ai->ai_socktype != SOCK_RAW) { + ar->ar_gai_errno = EAI_SOCKTYPE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_socktype == SOCK_RAW && + get_port(as->as.ai.servname, NULL, 1) != 0) { + ar->ar_gai_errno = EAI_SERVICE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Restrict result set to configured address families */ + if (ai->ai_flags & AI_ADDRCONFIG) { + if (addrconfig_setup(as) == -1) { + ar->ar_errno = errno; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + } + + /* Make sure there is at least a valid combination */ + for (i = 0; matches[i].family != -1; i++) + if (MATCH_FAMILY(ai->ai_family, i) && + MATCH_SOCKTYPE(ai->ai_socktype, i) && + MATCH_PROTO(ai->ai_protocol, i)) + break; + if (matches[i].family == -1) { +#ifdef EAI_BADHINTS + ar->ar_gai_errno = EAI_BADHINTS; +#else + ar->ar_gai_errno = EAI_FAIL; +#endif + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_UDP) + as->as.ai.port_udp = get_port(as->as.ai.servname, "udp", + as->as.ai.hints.ai_flags & AI_NUMERICSERV); + if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_TCP) + as->as.ai.port_tcp = get_port(as->as.ai.servname, "tcp", + as->as.ai.hints.ai_flags & AI_NUMERICSERV); + if (as->as.ai.port_tcp == -2 || as->as.ai.port_udp == -2 || + (as->as.ai.port_tcp == -1 && as->as.ai.port_udp == -1) || + (ai->ai_protocol && (as->as.ai.port_udp == -1 || + as->as.ai.port_tcp == -1))) { + ar->ar_gai_errno = EAI_SERVICE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + ar->ar_gai_errno = 0; + + /* If hostname is NULL, use local address */ + if (as->as.ai.hostname == NULL) { + for (family = iter_family(as, 1); + family != -1; + family = iter_family(as, 0)) { + /* + * We could use statically built sockaddrs for + * those, rather than parsing over and over. + */ + if (family == PF_INET) + str = (ai->ai_flags & AI_PASSIVE) ? \ + "0.0.0.0" : "127.0.0.1"; + else /* PF_INET6 */ + str = (ai->ai_flags & AI_PASSIVE) ? \ + "::" : "::1"; + /* This can't fail */ + _asr_sockaddr_from_str(&sa.sa, family, str); + if ((r = addrinfo_add(as, &sa.sa, NULL))) { + ar->ar_gai_errno = r; + break; + } + } + if (ar->ar_gai_errno == 0 && as->as_count == 0) { + ar->ar_gai_errno = EAI_NODATA; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Try numeric addresses first */ + for (family = iter_family(as, 1); + family != -1; + family = iter_family(as, 0)) { + + if (_asr_sockaddr_from_str(&sa.sa, family, + as->as.ai.hostname) == -1) + continue; + + if ((r = addrinfo_add(as, &sa.sa, NULL))) + ar->ar_gai_errno = r; + break; + } + if (ar->ar_gai_errno || as->as_count) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_flags & AI_NUMERICHOST) { + ar->ar_gai_errno = EAI_NONAME; + 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; + } + as->as_family_idx = 0; + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_NEXT_FAMILY: + as->as_family_idx += 1; + if (as->as.ai.hints.ai_family != AF_UNSPEC || + AS_FAMILY(as) == -1) { + /* The family was specified, or we have tried all + * families with this DB. + */ + if (as->as_count) { + ar->ar_gai_errno = 0; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + } + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_NEXT_DOMAIN: + /* domain search is only for dns */ + if (AS_DB(as) != ASR_DB_DNS) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + as->as_family_idx = 0; + + free(as->as.ai.fqdn); + as->as.ai.fqdn = NULL; + r = _asr_iter_domain(as, as->as.ai.hostname, fqdn, sizeof(fqdn)); + if (r == -1) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + if (r == 0) { + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + as->as.ai.fqdn = strdup(fqdn); + if (as->as.ai.fqdn == NULL) { + ar->ar_gai_errno = EAI_MEMORY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_SAME_DB: + /* query the current DB again */ + switch (AS_DB(as)) { + case ASR_DB_DNS: + if (as->as.ai.fqdn == NULL) { + /* First try, initialize domain iteration */ + as->as_dom_flags = 0; + as->as_dom_step = DOM_INIT; + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + } + + family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? + AS_FAMILY(as) : as->as.ai.hints.ai_family; + + if (family == AF_INET && + as->as_flags & ASYNC_NO_INET) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } else if (family == AF_INET6 && + as->as_flags & ASYNC_NO_INET6) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } + + as->as_subq = _res_query_async_ctx(as->as.ai.fqdn, + C_IN, (family == AF_INET6) ? T_AAAA : T_A, + as->as_ctx); + + if (as->as_subq == NULL) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_DB_FILE: + f = fopen(_PATH_HOSTS, "re"); + if (f == NULL) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? + AS_FAMILY(as) : as->as.ai.hints.ai_family; + + r = addrinfo_from_file(as, family, f); + if (r == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_FAMILY); + fclose(f); + break; + + default: + async_set_state(as, ASR_STATE_NEXT_DB); + } + 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_datalen == -1) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } + + r = addrinfo_from_pkt(as, ar->ar_data, ar->ar_datalen); + if (r == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_FAMILY); + free(ar->ar_data); + break; + + case ASR_STATE_NOT_FOUND: + /* No result found. Maybe we can try again. */ + if (as->as_flags & ASYNC_AGAIN) + ar->ar_gai_errno = EAI_AGAIN; + else + ar->ar_gai_errno = EAI_NODATA; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_gai_errno == 0) { + ar->ar_count = as->as_count; + ar->ar_addrinfo = as->as.ai.aifirst; + as->as.ai.aifirst = NULL; + } else { + ar->ar_count = 0; + ar->ar_addrinfo = NULL; + } + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Retreive the port number for the service name "servname" and + * the protocol "proto". + */ +static int +get_port(const char *servname, const char *proto, int numonly) +{ +#ifdef HAVE_GETSERVBYNAME_R_4_ARGS + struct servent se; +#endif +#ifdef HAVE_STRUCT_SERVENT_DATA + struct servent_data sed; +#endif + int port; + const char *e; + + if (servname == NULL) + return (0); + + e = NULL; + port = strtonum(servname, 0, USHRT_MAX, &e); + if (e == NULL) + return (port); + if (errno == ERANGE) + return (-2); /* invalid */ + if (numonly) + return (-2); + + port = -1; +#ifdef HAVE_STRUCT_SERVENT_DATA + memset(&sed, 0, sizeof(sed)); +#endif +#ifdef HAVE_GETSERVBYNAME_R_4_ARGS + if (getservbyname_r(servname, proto, &se, &sed) != -1) + port = ntohs(se.s_port); +#endif +#ifdef HAVE_ENDSERVENT_R + endservent_r(&sed); +#endif + + return (port); +} + +/* + * Iterate over the address families that are to be queried. Use the + * list on the async context, unless a specific family was given in hints. + */ +static int +iter_family(struct asr_query *as, int first) +{ + if (first) { + as->as_family_idx = 0; + if (as->as.ai.hints.ai_family != PF_UNSPEC) + return as->as.ai.hints.ai_family; + return AS_FAMILY(as); + } + + if (as->as.ai.hints.ai_family != PF_UNSPEC) + return (-1); + + as->as_family_idx++; + + return AS_FAMILY(as); +} + +/* + * Use the sockaddr at "sa" to extend the result list on the "as" context, + * with the specified canonical name "cname". This function adds one + * entry per protocol/socktype match. + */ +static int +addrinfo_add(struct asr_query *as, const struct sockaddr *sa, const char *cname) +{ + struct addrinfo *ai; + int i, port, proto; + + for (i = 0; matches[i].family != -1; i++) { + if (matches[i].family != sa->sa_family || + !MATCH_SOCKTYPE(as->as.ai.hints.ai_socktype, i) || + !MATCH_PROTO(as->as.ai.hints.ai_protocol, i)) + continue; + + proto = as->as.ai.hints.ai_protocol; + if (!proto) + proto = matches[i].protocol; + + if (proto == IPPROTO_TCP) + port = as->as.ai.port_tcp; + else if (proto == IPPROTO_UDP) + port = as->as.ai.port_udp; + else + port = 0; + + /* servname specified, but not defined for this protocol */ + if (port == -1) + continue; + + ai = calloc(1, sizeof(*ai) + SA_LEN(sa)); + if (ai == NULL) + return (EAI_MEMORY); + ai->ai_family = sa->sa_family; + ai->ai_socktype = matches[i].socktype; + ai->ai_protocol = proto; + ai->ai_flags = as->as.ai.hints.ai_flags; + ai->ai_addrlen = SA_LEN(sa); + ai->ai_addr = (void *)(ai + 1); + if (cname && + as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) { + if ((ai->ai_canonname = strdup(cname)) == NULL) { + free(ai); + return (EAI_MEMORY); + } + } + memmove(ai->ai_addr, sa, SA_LEN(sa)); + if (sa->sa_family == PF_INET) + ((struct sockaddr_in *)ai->ai_addr)->sin_port = + htons(port); + else if (sa->sa_family == PF_INET6) + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port = + htons(port); + + if (as->as.ai.aifirst == NULL) + as->as.ai.aifirst = ai; + if (as->as.ai.ailast) + as->as.ai.ailast->ai_next = ai; + as->as.ai.ailast = ai; + as->as_count += 1; + } + + return (0); +} + +void +asr_freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *ai_next; + + while (ai) { + ai_next = ai->ai_next; + if (ai->ai_canonname) + free(ai->ai_canonname); + free(ai); + ai = ai_next; + } +} + +static int +addrinfo_from_file(struct asr_query *as, int family, FILE *f) +{ + char *tokens[MAXTOKEN], *c, buf[ASR_BUFSIZ + 1]; + int n, i; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } u; + + for (;;) { + n = _asr_parse_namedb_line(f, tokens, MAXTOKEN, buf, sizeof(buf)); + if (n == -1) + break; /* ignore errors reading the file */ + + for (i = 1; i < n; i++) { + if (strcasecmp(as->as.ai.hostname, tokens[i])) + continue; + if (_asr_sockaddr_from_str(&u.sa, family, tokens[0]) == -1) + continue; + break; + } + if (i == n) + continue; + + if (as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) + c = tokens[1]; + else + c = NULL; + + if (addrinfo_add(as, &u.sa, c)) + return (-1); /* errno set */ + } + return (0); +} + +static int +addrinfo_from_pkt(struct asr_query *as, char *pkt, size_t pktlen) +{ + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int i; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } u; + char buf[MAXDNAME], *c; + + _asr_unpack_init(&p, pkt, pktlen); + _asr_unpack_header(&p, &h); + for (; h.qdcount; h.qdcount--) + _asr_unpack_query(&p, &q); + + for (i = 0; i < h.ancount; i++) { + _asr_unpack_rr(&p, &rr); + if (rr.rr_type != q.q_type || + rr.rr_class != q.q_class) + continue; + + memset(&u, 0, sizeof u); + if (rr.rr_type == T_A) { +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + u.sain.sin_len = sizeof u.sain; +#endif + u.sain.sin_family = AF_INET; + u.sain.sin_addr = rr.rr.in_a.addr; + u.sain.sin_port = 0; + } else if (rr.rr_type == T_AAAA) { +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + u.sain6.sin6_len = sizeof u.sain6; +#endif + u.sain6.sin6_family = AF_INET6; + u.sain6.sin6_addr = rr.rr.in_aaaa.addr6; + u.sain6.sin6_port = 0; + } else + continue; + + if (as->as.ai.hints.ai_flags & AI_CANONNAME) { + _asr_strdname(rr.rr_dname, buf, sizeof buf); + buf[strlen(buf) - 1] = '\0'; + c = res_hnok(buf) ? buf : NULL; + } else if (as->as.ai.hints.ai_flags & AI_FQDN) + c = as->as.ai.fqdn; + else + c = NULL; + + if (addrinfo_add(as, &u.sa, c)) + return (-1); /* errno set */ + } + return (0); +} + +static int +addrconfig_setup(struct asr_query *as) +{ + struct ifaddrs *ifa, *ifa0; + struct sockaddr_in *sinp; + struct sockaddr_in6 *sin6p; + + if (getifaddrs(&ifa0) == -1) + return (-1); + + as->as_flags |= ASYNC_NO_INET | ASYNC_NO_INET6; + + for (ifa = ifa0; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + + switch (ifa->ifa_addr->sa_family) { + case PF_INET: + sinp = (struct sockaddr_in *)ifa->ifa_addr; + + if (sinp->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + continue; + + as->as_flags &= ~ASYNC_NO_INET; + break; + case PF_INET6: + sin6p = (struct sockaddr_in6 *)ifa->ifa_addr; + + if (IN6_IS_ADDR_LOOPBACK(&sin6p->sin6_addr)) + continue; + + if (IN6_IS_ADDR_LINKLOCAL(&sin6p->sin6_addr)) + continue; + + as->as_flags &= ~ASYNC_NO_INET6; + break; + } + } + + freeifaddrs(ifa0); + + return (0); +} diff --git a/openbsd-compat/libasr/gethostnamadr.c b/openbsd-compat/libasr/gethostnamadr.c new file mode 100644 index 00000000..2fce46b3 --- /dev/null +++ b/openbsd-compat/libasr/gethostnamadr.c @@ -0,0 +1,200 @@ +/* $OpenBSD: gethostnamadr.c,v 1.13 2015/09/14 07:38:37 guenther Exp $ */ +/* + * Copyright (c) 2012,2013 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 <sys/param.h> /* ALIGN */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +static int _gethostbyname(const char *, int, struct hostent *, char *, size_t, + int *); +static int _fillhostent(const struct hostent *, struct hostent *, char *, + size_t); + +static struct hostent _hostent; +static char _entbuf[4096]; + +static char *_empty[] = { NULL, }; + +static int +_fillhostent(const struct hostent *h, struct hostent *r, char *buf, size_t len) +{ + char **ptr, *end, *pos; + size_t n, i; + int naliases, naddrs; + + bzero(buf, len); + bzero(r, sizeof(*r)); + r->h_aliases = _empty; + r->h_addr_list = _empty; + + end = buf + len; + ptr = (char **)ALIGN(buf); + + if ((char *)ptr >= end) + return (ERANGE); + + for (naliases = 0; h->h_aliases[naliases]; naliases++) + ; + for (naddrs = 0; h->h_addr_list[naddrs]; naddrs++) + ; + + pos = (char *)(ptr + (naliases + 1) + (naddrs + 1)); + if (pos >= end) + return (ERANGE); + + r->h_name = NULL; + r->h_addrtype = h->h_addrtype; + r->h_length = h->h_length; + r->h_aliases = ptr; + r->h_addr_list = ptr + naliases + 1; + + n = strlcpy(pos, h->h_name, end - pos); + if (n >= end - pos) + return (ERANGE); + r->h_name = pos; + pos += n + 1; + + for (i = 0; i < naliases; i++) { + n = strlcpy(pos, h->h_aliases[i], end - pos); + if (n >= end - pos) + return (ERANGE); + r->h_aliases[i] = pos; + pos += n + 1; + } + + pos = (char *)ALIGN(pos); + if (pos >= end) + return (ERANGE); + + for (i = 0; i < naddrs; i++) { + if (r->h_length > end - pos) + return (ERANGE); + memmove(pos, h->h_addr_list[i], r->h_length); + r->h_addr_list[i] = pos; + pos += r->h_length; + } + + return (0); +} + +static int +_gethostbyname(const char *name, int af, struct hostent *ret, char *buf, + size_t buflen, int *h_errnop) +{ + struct asr_query *as; + struct asr_result ar; + int r; + + if (af == -1) + as = gethostbyname_async(name, NULL); + else + as = gethostbyname2_async(name, af, NULL); + + if (as == NULL) + return (errno); + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + *h_errnop = ar.ar_h_errno; + if (ar.ar_hostent == NULL) + return (0); + + r = _fillhostent(ar.ar_hostent, ret, buf, buflen); + free(ar.ar_hostent); + + return (r); +} + +struct hostent * +gethostbyname(const char *name) +{ + struct hostent *h; + + res_init(); + + if (_res.options & RES_USE_INET6 && + (h = gethostbyname2(name, AF_INET6))) + return (h); + + return gethostbyname2(name, AF_INET); +} +DEF_WEAK(gethostbyname); + +struct hostent * +gethostbyname2(const char *name, int af) +{ + int r; + + res_init(); + + r = _gethostbyname(name, af, &_hostent, _entbuf, sizeof(_entbuf), + &h_errno); + if (r) { + h_errno = NETDB_INTERNAL; + errno = r; + } + + if (h_errno) + return (NULL); + + return (&_hostent); +} +DEF_WEAK(gethostbyname2); + +struct hostent * +gethostbyaddr(const void *addr, socklen_t len, int af) +{ + struct asr_query *as; + struct asr_result ar; + int r; + + res_init(); + + as = gethostbyaddr_async(addr, len, af, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_hostent == NULL) + return (NULL); + + r = _fillhostent(ar.ar_hostent, &_hostent, _entbuf, sizeof(_entbuf)); + free(ar.ar_hostent); + + if (r) { + h_errno = NETDB_INTERNAL; + errno = r; + return (NULL); + } + + return (&_hostent); +} 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; +} diff --git a/openbsd-compat/libasr/getnameinfo.c b/openbsd-compat/libasr/getnameinfo.c new file mode 100644 index 00000000..7bee468d --- /dev/null +++ b/openbsd-compat/libasr/getnameinfo.c @@ -0,0 +1,205 @@ +/* $OpenBSD: getnameinfo.c,v 1.9 2019/07/03 03:24:03 deraadt 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 <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> + +static size_t asr_print_addr(const struct sockaddr *, char *, size_t); +static size_t asr_print_port(const struct sockaddr *, const char *, char *, size_t); + +#define SA_IN(sa) ((struct sockaddr_in*)(sa)) +#define SA_IN6(sa) ((struct sockaddr_in6*)(sa)) + +/* + * Print the textual representation (as given by inet_ntop(3)) of the address + * set in "sa". + * + * Return the total length of the string it tried to create or 0 if an error + * occured, in which case errno is set. On success, the constructed string + * is guaranteed to be NUL-terminated. Overflow must be detected by checking + * the returned size against buflen. + * + */ +static size_t +asr_print_addr(const struct sockaddr *sa, char *buf, size_t buflen) +{ + unsigned int ifidx; + char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + char scope[IF_NAMESIZE + 1], *ifname; + const void *addr; + size_t s; + + switch(sa->sa_family) { + case AF_INET: + addr = &SA_IN(sa)->sin_addr; + break; + case AF_INET6: + addr = &SA_IN6(sa)->sin6_addr; + break; + default: + errno = EINVAL; + return (0); + } + + if (inet_ntop(sa->sa_family, addr, tmp, sizeof(tmp)) == NULL) + return (0); /* errno set */ + + s = strlcpy(buf, tmp, buflen); + + if (sa->sa_family == AF_INET6 && SA_IN6(sa)->sin6_scope_id) { + + scope[0] = SCOPE_DELIMITER; + scope[1] = '\0'; + + ifidx = SA_IN6(sa)->sin6_scope_id; + ifname = NULL; + + if (IN6_IS_ADDR_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) || + IN6_IS_ADDR_MC_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) || + IN6_IS_ADDR_MC_INTFACELOCAL(&(SA_IN6(sa)->sin6_addr))) + ifname = if_indextoname(ifidx, scope + 1); + + if (ifname == NULL) + (void)snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx); + + if (s < buflen) + (void)strlcat(buf, scope, buflen); + + s += strlen(scope); + } + + return (s); +} + +/* + * Print the textual representation of the port set on "sa". + * + * If proto is not NULL, it is used as parameter to "getservbyport_r(3)" to + * return a service name. If it's not set, or if no matching service is found, + * it prints the portno. + * + * Return the total length of the string it tried to create or 0 if an error + * occured, in which case errno is set. On success, the constructed string + * is guaranteed to be NUL-terminated. Overflow must be detected by checking + * the returned size against buflen. + */ +static size_t +asr_print_port(const struct sockaddr *sa, const char *proto, char *buf, size_t buflen) +{ + struct servent s; + struct servent_data sd; + int port, r, saved_errno; + size_t n; + + switch(sa->sa_family) { + case AF_INET: + port = SA_IN(sa)->sin_port; + break; + case AF_INET6: + port = SA_IN6(sa)->sin6_port; + break; + default: + errno = EINVAL; + return (0); + } + + if (proto) { + memset(&sd, 0, sizeof (sd)); + saved_errno = errno; + if (getservbyport_r(port, proto, &s, &sd) != -1) { + n = strlcpy(buf, s.s_name, buflen); + endservent_r(&sd); + return (n); + } + errno = saved_errno; + } + + r = snprintf(buf, buflen, "%u", ntohs(port)); + if (r < 0 || r >= buflen) /* Actually, this can not happen */ + return (0); + + return (r); +} + +int +getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + struct asr_query *as; + struct asr_result ar; + int saved_errno = errno; + const char *proto; + size_t r; + + /* + * Take a shortcut if we don't care about hostname, + * or if NI_NUMERICHOST is set. + */ + if (host == NULL || hostlen == 0 || + (host && hostlen && (flags & NI_NUMERICHOST))) { + if (host) { + r = asr_print_addr(sa, host, hostlen); + if (r == 0) + return (EAI_SYSTEM); /* errno set */ + if (r >= hostlen) + return (EAI_OVERFLOW); + } + + if (serv && servlen) { + if (flags & NI_NUMERICSERV) + proto = NULL; + else + proto = (flags & NI_DGRAM) ? "udp" : "tcp"; + r = asr_print_port(sa, proto, serv, servlen); + if (r == 0) + return (EAI_SYSTEM); /* errno set */ + if (r >= servlen) + return (EAI_OVERFLOW); + } + + errno = saved_errno; + return (0); + } + + res_init(); + + as = getnameinfo_async(sa, salen, host, hostlen, serv, servlen, flags, + NULL); + if (as == NULL) { + if (errno == ENOMEM) { + errno = saved_errno; + return (EAI_MEMORY); + } + return (EAI_SYSTEM); + } + + asr_run_sync(as, &ar); + if (ar.ar_gai_errno == EAI_SYSTEM) + errno = ar.ar_errno; + + return (ar.ar_gai_errno); +} +DEF_WEAK(getnameinfo); diff --git a/openbsd-compat/libasr/getnameinfo_async.c b/openbsd-compat/libasr/getnameinfo_async.c new file mode 100644 index 00000000..faba8860 --- /dev/null +++ b/openbsd-compat/libasr/getnameinfo_async.c @@ -0,0 +1,300 @@ +/* $OpenBSD: getnameinfo_async.c,v 1.14 2019/07/03 03:24:03 deraadt 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 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); +#else + r = -1; +#endif + if (r != -1) { + r = strlcpy(buf, s.s_name, buflen) >= buflen; +#ifdef HAVE_ENDSERVENT_R + endservent_r(&sd); +#endif + return (r ? -1 : 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_NODELOCAL(&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); +} diff --git a/openbsd-compat/libasr/getnetnamadr.c b/openbsd-compat/libasr/getnetnamadr.c new file mode 100644 index 00000000..141b4a9a --- /dev/null +++ b/openbsd-compat/libasr/getnetnamadr.c @@ -0,0 +1,134 @@ +/* $OpenBSD: getnetnamadr.c,v 1.9 2015/01/16 16:48:51 deraadt 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 <sys/param.h> /* ALIGN */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> + +static void _fillnetent(const struct netent *, struct netent *, char *buf, + size_t); + +static struct netent _netent; +static char _entbuf[4096]; + +static char *_empty[] = { NULL, }; + +static void +_fillnetent(const struct netent *e, struct netent *r, char *buf, size_t len) +{ + char **ptr, *end, *pos; + size_t n, i; + int naliases; + + bzero(buf, len); + bzero(r, sizeof(*r)); + r->n_aliases = _empty; + + end = buf + len; + ptr = (char **)ALIGN(buf); + + if ((char *)ptr >= end) + return; + + for (naliases = 0; e->n_aliases[naliases]; naliases++) + ; + + r->n_name = NULL; + r->n_addrtype = e->n_addrtype; + r->n_net = e->n_net; + r->n_aliases = ptr; + + pos = (char *)(ptr + (naliases + 1)); + if (pos > end) + r->n_aliases = _empty; + + n = strlcpy(pos, e->n_name, end - pos); + if (n >= end - pos) + return; + r->n_name = pos; + pos += n + 1; + + for (i = 0; i < naliases; i++) { + n = strlcpy(pos, e->n_aliases[i], end - pos); + if (n >= end - pos) + return; + r->n_aliases[i] = pos; + pos += n + 1; + } +} + +struct netent * +getnetbyname(const char *name) +{ + struct asr_query *as; + struct asr_result ar; + + res_init(); + + as = getnetbyname_async(name, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_netent == NULL) + return (NULL); + + _fillnetent(ar.ar_netent, &_netent, _entbuf, sizeof(_entbuf)); + free(ar.ar_netent); + + return (&_netent); +} + +struct netent * +getnetbyaddr(in_addr_t net, int type) +{ + struct asr_query *as; + struct asr_result ar; + + res_init(); + + as = getnetbyaddr_async(net, type, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_netent == NULL) + return (NULL); + + _fillnetent(ar.ar_netent, &_netent, _entbuf, sizeof(_entbuf)); + free(ar.ar_netent); + + return (&_netent); +} diff --git a/openbsd-compat/libasr/getnetnamadr_async.c b/openbsd-compat/libasr/getnetnamadr_async.c new file mode 100644 index 00000000..1e02d008 --- /dev/null +++ b/openbsd-compat/libasr/getnetnamadr_async.c @@ -0,0 +1,52 @@ +/* $OpenBSD: getnetnamadr_async.c,v 1.26 2018/04/28 15:16:49 schwarze 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 <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> +#include <asr.h> + +#include "asr_private.h" + +struct asr_query * +getnetbyname_async(const char *name, void *asr) +{ + struct asr_query *as; + + if ((as = gethostbyname_async(name, asr)) != NULL) + as->as_flags |= ASYNC_GETNET; + return (as); +} +DEF_WEAK(getnetbyname_async); + +struct asr_query * +getnetbyaddr_async(in_addr_t net, int family, void *asr) +{ + struct in_addr in; + struct asr_query *as; + + in.s_addr = htonl(net); + as = gethostbyaddr_async(&in, sizeof(in), family, asr); + if (as != NULL) + as->as_flags |= ASYNC_GETNET; + return (as); +} +DEF_WEAK(getnetbyaddr_async); diff --git a/openbsd-compat/libasr/getrrsetbyname.c b/openbsd-compat/libasr/getrrsetbyname.c new file mode 100644 index 00000000..24df2c8b --- /dev/null +++ b/openbsd-compat/libasr/getrrsetbyname.c @@ -0,0 +1,83 @@ +/* $OpenBSD: getrrsetbyname.c,v 1.6 2015/09/14 07:38:37 guenther 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> + +int +getrrsetbyname(const char *name, unsigned int class, unsigned int type, + unsigned int flags, struct rrsetinfo **res) +{ + struct asr_query *as; + struct asr_result ar; + int r, saved_errno = errno; + + res_init(); + + as = getrrsetbyname_async(name, class, type, flags, NULL); + if (as == NULL) { + r = (errno == ENOMEM) ? ERRSET_NOMEMORY : ERRSET_FAIL; + errno = saved_errno; + return (r); + } + + asr_run_sync(as, &ar); + + *res = ar.ar_rrsetinfo; + + return (ar.ar_rrset_errno); +} + +/* from net/getrrsetbyname.c */ +void +freerrset(struct rrsetinfo *rrset) +{ + u_int16_t i; + + if (rrset == NULL) + return; + + if (rrset->rri_rdatas) { + for (i = 0; i < rrset->rri_nrdatas; i++) { + if (rrset->rri_rdatas[i].rdi_data == NULL) + break; + free(rrset->rri_rdatas[i].rdi_data); + } + free(rrset->rri_rdatas); + } + + if (rrset->rri_sigs) { + for (i = 0; i < rrset->rri_nsigs; i++) { + if (rrset->rri_sigs[i].rdi_data == NULL) + break; + free(rrset->rri_sigs[i].rdi_data); + } + free(rrset->rri_sigs); + } + + if (rrset->rri_name) + free(rrset->rri_name); + free(rrset); +} +DEF_WEAK(freerrset); diff --git a/openbsd-compat/libasr/getrrsetbyname_async.c b/openbsd-compat/libasr/getrrsetbyname_async.c new file mode 100644 index 00000000..9ff7f6c7 --- /dev/null +++ b/openbsd-compat/libasr/getrrsetbyname_async.c @@ -0,0 +1,590 @@ +/* $OpenBSD: getrrsetbyname_async.c,v 1.11 2017/02/23 17:04:02 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 <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int getrrsetbyname_async_run(struct asr_query *, struct asr_result *); +static void get_response(struct asr_result *, const char *, int); + +struct asr_query * +getrrsetbyname_async(const char *hostname, unsigned int rdclass, + unsigned int rdtype, unsigned int flags, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_GETRRSETBYNAME)) == NULL) + goto abort; /* errno set */ + as->as_run = getrrsetbyname_async_run; + + as->as.rrset.flags = flags; + as->as.rrset.class = rdclass; + as->as.rrset.type = rdtype; + as->as.rrset.name = strdup(hostname); + if (as->as.rrset.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(getrrsetbyname_async); + +static int +getrrsetbyname_async_run(struct asr_query *as, struct asr_result *ar) +{ + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + /* Check for invalid class and type. */ + if (as->as.rrset.class > 0xffff || as->as.rrset.type > 0xffff) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Do not allow queries of class or type ANY. */ + if (as->as.rrset.class == 0xff || as->as.rrset.type == 0xff) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Do not allow flags yet, unimplemented. */ + if (as->as.rrset.flags) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Create a delegate the lookup to a subquery. */ + as->as_subq = _res_query_async_ctx( + as->as.rrset.name, + as->as.rrset.class, + as->as.rrset.type, + as->as_ctx); + if (as->as_subq == NULL) { + ar->ar_rrset_errno = ERRSET_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_STATE_SUBQUERY: + + if ((asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + as->as_subq = NULL; + + /* No packet received.*/ + if (ar->ar_datalen == -1) { + switch (ar->ar_h_errno) { + case HOST_NOT_FOUND: + ar->ar_rrset_errno = ERRSET_NONAME; + break; + case NO_DATA: + ar->ar_rrset_errno = ERRSET_NODATA; + break; + default: + ar->ar_rrset_errno = ERRSET_FAIL; + break; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Got a packet but no answer. */ + if (ar->ar_count == 0) { + free(ar->ar_data); + switch (ar->ar_rcode) { + case NXDOMAIN: + ar->ar_rrset_errno = ERRSET_NONAME; + break; + case NOERROR: + ar->ar_rrset_errno = ERRSET_NODATA; + break; + default: + ar->ar_rrset_errno = ERRSET_FAIL; + break; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + get_response(ar, ar->ar_data, ar->ar_datalen); + free(ar->ar_data); + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_rrset_errno) + ar->ar_rrsetinfo = NULL; + return (ASYNC_DONE); + + default: + ar->ar_rrset_errno = ERRSET_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* The rest of this file is taken from the orignal implementation. */ + +/* $OpenBSD: getrrsetbyname_async.c,v 1.11 2017/02/23 17:04:02 eric Exp $ */ + +/* + * Copyright (c) 2001 Jakob Schlyter. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Portions Copyright (c) 1999-2001 Internet Software Consortium. + * + * 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 INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +#define MAXPACKET 1024*64 + +struct dns_query { + char *name; + u_int16_t type; + u_int16_t class; + struct dns_query *next; +}; + +struct dns_rr { + char *name; + u_int16_t type; + u_int16_t class; + u_int16_t ttl; + u_int16_t size; + void *rdata; + struct dns_rr *next; +}; + +struct dns_response { + HEADER header; + struct dns_query *query; + struct dns_rr *answer; + struct dns_rr *authority; + struct dns_rr *additional; +}; + +static struct dns_response *parse_dns_response(const u_char *, int); +static struct dns_query *parse_dns_qsection(const u_char *, int, + const u_char **, int); +static struct dns_rr *parse_dns_rrsection(const u_char *, int, const u_char **, + int); + +static void free_dns_query(struct dns_query *); +static void free_dns_rr(struct dns_rr *); +static void free_dns_response(struct dns_response *); + +static int count_dns_rr(struct dns_rr *, u_int16_t, u_int16_t); + +static void +get_response(struct asr_result *ar, const char *pkt, int pktlen) +{ + struct rrsetinfo *rrset = NULL; + struct dns_response *response = NULL; + struct dns_rr *rr; + struct rdatainfo *rdata; + unsigned int index_ans, index_sig; + + /* parse result */ + response = parse_dns_response(pkt, pktlen); + if (response == NULL) { + ar->ar_rrset_errno = ERRSET_FAIL; + goto fail; + } + + if (response->header.qdcount != 1) { + ar->ar_rrset_errno = ERRSET_FAIL; + goto fail; + } + + /* initialize rrset */ + rrset = calloc(1, sizeof(struct rrsetinfo)); + if (rrset == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + rrset->rri_rdclass = response->query->class; + rrset->rri_rdtype = response->query->type; + rrset->rri_ttl = response->answer->ttl; + rrset->rri_nrdatas = response->header.ancount; + + /* check for authenticated data */ + if (response->header.ad == 1) + rrset->rri_flags |= RRSET_VALIDATED; + + /* copy name from answer section */ + rrset->rri_name = strdup(response->answer->name); + if (rrset->rri_name == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* count answers */ + rrset->rri_nrdatas = count_dns_rr(response->answer, rrset->rri_rdclass, + rrset->rri_rdtype); + rrset->rri_nsigs = count_dns_rr(response->answer, rrset->rri_rdclass, + T_RRSIG); + + /* allocate memory for answers */ + rrset->rri_rdatas = calloc(rrset->rri_nrdatas, + sizeof(struct rdatainfo)); + if (rrset->rri_rdatas == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* allocate memory for signatures */ + rrset->rri_sigs = calloc(rrset->rri_nsigs, sizeof(struct rdatainfo)); + if (rrset->rri_sigs == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* copy answers & signatures */ + for (rr = response->answer, index_ans = 0, index_sig = 0; + rr; rr = rr->next) { + + rdata = NULL; + + if (rr->class == rrset->rri_rdclass && + rr->type == rrset->rri_rdtype) + rdata = &rrset->rri_rdatas[index_ans++]; + + if (rr->class == rrset->rri_rdclass && + rr->type == T_RRSIG) + rdata = &rrset->rri_sigs[index_sig++]; + + if (rdata) { + rdata->rdi_length = rr->size; + rdata->rdi_data = malloc(rr->size); + + if (rdata->rdi_data == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + memcpy(rdata->rdi_data, rr->rdata, rr->size); + } + } + free_dns_response(response); + + ar->ar_rrsetinfo = rrset; + ar->ar_rrset_errno = ERRSET_SUCCESS; + return; + +fail: + if (rrset != NULL) + freerrset(rrset); + if (response != NULL) + free_dns_response(response); +} + +/* + * DNS response parsing routines + */ +static struct dns_response * +parse_dns_response(const u_char *answer, int size) +{ + struct dns_response *resp; + const u_char *cp; + + /* allocate memory for the response */ + resp = calloc(1, sizeof(*resp)); + if (resp == NULL) + return (NULL); + + /* initialize current pointer */ + cp = answer; + + /* copy header */ + memcpy(&resp->header, cp, HFIXEDSZ); + cp += HFIXEDSZ; + + /* fix header byte order */ + resp->header.qdcount = ntohs(resp->header.qdcount); + resp->header.ancount = ntohs(resp->header.ancount); + resp->header.nscount = ntohs(resp->header.nscount); + resp->header.arcount = ntohs(resp->header.arcount); + + /* there must be at least one query */ + if (resp->header.qdcount < 1) { + free_dns_response(resp); + return (NULL); + } + + /* parse query section */ + resp->query = parse_dns_qsection(answer, size, &cp, + resp->header.qdcount); + if (resp->header.qdcount && resp->query == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse answer section */ + resp->answer = parse_dns_rrsection(answer, size, &cp, + resp->header.ancount); + if (resp->header.ancount && resp->answer == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse authority section */ + resp->authority = parse_dns_rrsection(answer, size, &cp, + resp->header.nscount); + if (resp->header.nscount && resp->authority == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse additional section */ + resp->additional = parse_dns_rrsection(answer, size, &cp, + resp->header.arcount); + if (resp->header.arcount && resp->additional == NULL) { + free_dns_response(resp); + return (NULL); + } + + return (resp); +} + +static struct dns_query * +parse_dns_qsection(const u_char *answer, int size, const u_char **cp, int count) +{ + struct dns_query *head, *curr, *prev; + int i, length; + char name[MAXDNAME]; + + for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) { + + /* allocate and initialize struct */ + curr = calloc(1, sizeof(struct dns_query)); + if (curr == NULL) { + free_dns_query(head); + return (NULL); + } + if (head == NULL) + head = curr; + if (prev != NULL) + prev->next = curr; + + /* name */ + length = dn_expand(answer, answer + size, *cp, name, + sizeof(name)); + if (length < 0) { + free_dns_query(head); + return (NULL); + } + curr->name = strdup(name); + if (curr->name == NULL) { + free_dns_query(head); + return (NULL); + } + *cp += length; + + /* type */ + curr->type = _getshort(*cp); + *cp += INT16SZ; + + /* class */ + curr->class = _getshort(*cp); + *cp += INT16SZ; + } + + return (head); +} + +static struct dns_rr * +parse_dns_rrsection(const u_char *answer, int size, const u_char **cp, + int count) +{ + struct dns_rr *head, *curr, *prev; + int i, length; + char name[MAXDNAME]; + + for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) { + + /* allocate and initialize struct */ + curr = calloc(1, sizeof(struct dns_rr)); + if (curr == NULL) { + free_dns_rr(head); + return (NULL); + } + if (head == NULL) + head = curr; + if (prev != NULL) + prev->next = curr; + + /* name */ + length = dn_expand(answer, answer + size, *cp, name, + sizeof(name)); + if (length < 0) { + free_dns_rr(head); + return (NULL); + } + curr->name = strdup(name); + if (curr->name == NULL) { + free_dns_rr(head); + return (NULL); + } + *cp += length; + + /* type */ + curr->type = _getshort(*cp); + *cp += INT16SZ; + + /* class */ + curr->class = _getshort(*cp); + *cp += INT16SZ; + + /* ttl */ + curr->ttl = _getlong(*cp); + *cp += INT32SZ; + + /* rdata size */ + curr->size = _getshort(*cp); + *cp += INT16SZ; + + /* rdata itself */ + curr->rdata = malloc(curr->size); + if (curr->rdata == NULL) { + free_dns_rr(head); + return (NULL); + } + memcpy(curr->rdata, *cp, curr->size); + *cp += curr->size; + } + + return (head); +} + +static void +free_dns_query(struct dns_query *p) +{ + if (p == NULL) + return; + + if (p->name) + free(p->name); + free_dns_query(p->next); + free(p); +} + +static void +free_dns_rr(struct dns_rr *p) +{ + if (p == NULL) + return; + + if (p->name) + free(p->name); + if (p->rdata) + free(p->rdata); + free_dns_rr(p->next); + free(p); +} + +static void +free_dns_response(struct dns_response *p) +{ + if (p == NULL) + return; + + free_dns_query(p->query); + free_dns_rr(p->answer); + free_dns_rr(p->authority); + free_dns_rr(p->additional); + free(p); +} + +static int +count_dns_rr(struct dns_rr *p, u_int16_t class, u_int16_t type) +{ + int n = 0; + + while (p) { + if (p->class == class && p->type == type) + n++; + p = p->next; + } + + return (n); +} diff --git a/openbsd-compat/libasr/libasr.la b/openbsd-compat/libasr/libasr.la new file mode 100644 index 00000000..71346e8a --- /dev/null +++ b/openbsd-compat/libasr/libasr.la @@ -0,0 +1,41 @@ +# libasr.la - a libtool library file +# Generated by libtool (GNU libtool) 2.4.6 +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='libasr.0.dylib' + +# Names of this library. +library_names='libasr.0.dylib libasr.dylib' + +# The name of the static archive. +old_library='libasr.a' + +# Linker flags that cannot go in dependency_libs. +inherited_linker_flags=' ' + +# Libraries that this one depends upon. +dependency_libs=' -L/usr/local/Cellar/openssl@1.1/1.1.1d//lib -L/usr/local/lib -lz -lcrypto -lssl -levent -lresolv' + +# Names of additional weak libraries provided by this library +weak_library_names='' + +# Version information for libasr. +current=0 +age=0 +revision=3 + +# Is this an already installed library? +installed=no + +# Should we warn about portability when linking against -modules? +shouldnotlink=no + +# Files to dlopen/dlpreopen +dlopen='' +dlpreopen='' + +# Directory that this library needs to be installed in: +libdir='/tmp/lib' diff --git a/openbsd-compat/libasr/res_debug.c b/openbsd-compat/libasr/res_debug.c new file mode 100644 index 00000000..ca9c5ee0 --- /dev/null +++ b/openbsd-compat/libasr/res_debug.c @@ -0,0 +1,2 @@ +/* $OpenBSD: res_debug.c,v 1.1 2012/09/08 11:08:21 eric Exp $ */ +/* NOTHING */ diff --git a/openbsd-compat/libasr/res_init.c b/openbsd-compat/libasr/res_init.c new file mode 100644 index 00000000..04243c47 --- /dev/null +++ b/openbsd-compat/libasr/res_init.c @@ -0,0 +1,103 @@ +/* $OpenBSD: res_init.c,v 1.11 2019/06/17 05:54:45 otto 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 <sys/types.h> +#include <sys/socket.h> +#include <arpa/nameser.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" +#include "thread_private.h" + + +struct __res_state _res; +struct __res_state_ext _res_ext; + +int h_errno; + +int +res_init(void) +{ + static void *resinit_mutex; + struct asr_ctx *ac; + int i; + + ac = _asr_use_resolver(NULL); + + /* + * The first thread to call res_init() will setup the global _res + * structure from the async context, not overriding fields set early + * by the user. + */ + _MUTEX_LOCK(&resinit_mutex); + if (!(_res.options & RES_INIT)) { + if (_res.retry == 0) + _res.retry = ac->ac_nsretries; + if (_res.retrans == 0) + _res.retrans = ac->ac_nstimeout; + if (_res.options == 0) + _res.options = ac->ac_options; + if (_res.lookups[0] == '\0') + strlcpy(_res.lookups, ac->ac_db, sizeof(_res.lookups)); + + for (i = 0; i < ac->ac_nscount && i < MAXNS; i++) { + /* + * No need to check for length since we copy to a + * struct sockaddr_storage with a size of 256 bytes + * and sa_len has only 8 bits. + */ + memcpy(&_res_ext.nsaddr_list[i], ac->ac_ns[i], + ac->ac_ns[i]->sa_len); + if (ac->ac_ns[i]->sa_len <= sizeof(_res.nsaddr_list[i])) + memcpy(&_res.nsaddr_list[i], ac->ac_ns[i], + ac->ac_ns[i]->sa_len); + else + memset(&_res.nsaddr_list[i], 0, + sizeof(_res.nsaddr_list[i])); + } + _res.nscount = i; + _res.options |= RES_INIT; + } + _MUTEX_UNLOCK(&resinit_mutex); + + /* + * If the program is not threaded, we want to reflect (some) changes + * made by the user to the global _res structure. + * This is a bit of a hack: if there is already an async query on + * this context, it might change things in its back. It is ok + * as long as the user only uses the blocking resolver API. + * If needed we could consider cloning the context if there is + * a running query. + */ + if (!__isthreaded) { + ac->ac_nsretries = _res.retry; + ac->ac_nstimeout = _res.retrans; + ac->ac_options = _res.options; + strlcpy(ac->ac_db, _res.lookups, sizeof(ac->ac_db)); + ac->ac_dbcount = strlen(ac->ac_db); + } + + _asr_ctx_unref(ac); + + return (0); +} +DEF_WEAK(res_init); diff --git a/openbsd-compat/libasr/res_mkquery.c b/openbsd-compat/libasr/res_mkquery.c new file mode 100644 index 00000000..959ecc47 --- /dev/null +++ b/openbsd-compat/libasr/res_mkquery.c @@ -0,0 +1,119 @@ +/* $OpenBSD: res_mkquery.c,v 1.13 2019/01/14 06:49:42 otto 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/nameser.h> /* for MAXDNAME */ +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" + +/* This function is apparently needed by some ports. */ +int +res_mkquery(int op, const char *dname, int class, int type, + const unsigned char *data, int datalen, const unsigned char *newrr, + unsigned char *buf, int buflen) +{ + struct asr_ctx *ac; + struct asr_pack p; + struct asr_dns_header h; + char fqdn[MAXDNAME]; + char dn[MAXDNAME]; + + /* we currently only support QUERY */ + if (op != QUERY || data) + return (-1); + + if (dname[0] == '\0' || dname[strlen(dname) - 1] != '.') { + if (strlcpy(fqdn, dname, sizeof(fqdn)) >= sizeof(fqdn) || + strlcat(fqdn, ".", sizeof(fqdn)) >= sizeof(fqdn)) + return (-1); + dname = fqdn; + } + + if (_asr_dname_from_fqdn(dname, dn, sizeof(dn)) == -1) + return (-1); + + ac = _asr_use_resolver(NULL); + + memset(&h, 0, sizeof h); + h.id = res_randomid(); + if (ac->ac_options & RES_RECURSE) + h.flags |= RD_MASK; +#ifdef RES_USE_CD + if (ac->ac_options & RES_USE_CD) + h.flags |= CD_MASK; +#endif + h.qdcount = 1; + if (ac->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + h.arcount = 1; + + _asr_pack_init(&p, buf, buflen); + _asr_pack_header(&p, &h); + _asr_pack_query(&p, type, class, dn); + if (ac->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + _asr_pack_edns0(&p, MAXPACKETSZ, + ac->ac_options & RES_USE_DNSSEC); + + _asr_ctx_unref(ac); + + if (p.err) + return (-1); + + return (p.offset); +} + +/* + * This function is not documented, but used by sendmail. + * Put here because it uses asr_private.h too. + */ +int +res_querydomain(const char *name, + const char *domain, + int class, + int type, + u_char *answer, + int anslen) +{ + char fqdn[MAXDNAME], ndom[MAXDNAME]; + size_t n; + + /* we really want domain to end with a dot for now */ + if (domain && ((n = strlen(domain)) == 0 || domain[n - 1 ] != '.')) { + if (strlcpy(ndom, domain, sizeof(ndom)) >= sizeof(ndom) || + strlcat(ndom, ".", sizeof(ndom)) >= sizeof(ndom)) { + h_errno = NETDB_INTERNAL; + errno = EINVAL; + return (-1); + } + domain = ndom; + } + + if (_asr_make_fqdn(name, domain, fqdn, sizeof fqdn) == 0) { + h_errno = NETDB_INTERNAL; + errno = EINVAL; + return (-1); + } + + return (res_query(fqdn, class, type, answer, anslen)); +} diff --git a/openbsd-compat/libasr/res_query.c b/openbsd-compat/libasr/res_query.c new file mode 100644 index 00000000..3f891416 --- /dev/null +++ b/openbsd-compat/libasr/res_query.c @@ -0,0 +1,112 @@ +/* $OpenBSD: res_query.c,v 1.9 2015/10/05 02:57:16 guenther 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> +#include <stdlib.h> + +int +res_query(const char *name, int class, int type, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + h_errno = NO_RECOVERY; + errno = EINVAL; + return (-1); + } + + as = res_query_async(name, class, type, NULL); + if (as == NULL) { + if (errno == EINVAL) + h_errno = NO_RECOVERY; + else + h_errno = NETDB_INTERNAL; + return (-1); /* errno set */ + } + + asr_run_sync(as, &ar); + + if (ar.ar_errno) + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + + if (ar.ar_h_errno != NETDB_SUCCESS) + return (-1); + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} +DEF_WEAK(res_query); + +int +res_search(const char *name, int class, int type, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + h_errno = NO_RECOVERY; + errno = EINVAL; + return (-1); + } + + as = res_search_async(name, class, type, NULL); + if (as == NULL) { + if (errno == EINVAL) + h_errno = NO_RECOVERY; + else + h_errno = NETDB_INTERNAL; + return (-1); /* errno set */ + } + + asr_run_sync(as, &ar); + + if (ar.ar_errno) + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + + if (ar.ar_h_errno != NETDB_SUCCESS) + return (-1); + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} diff --git a/openbsd-compat/libasr/res_search_async.c b/openbsd-compat/libasr/res_search_async.c new file mode 100644 index 00000000..6436ab85 --- /dev/null +++ b/openbsd-compat/libasr/res_search_async.c @@ -0,0 +1,327 @@ +/* $OpenBSD: res_search_async.c,v 1.21 2017/02/27 10:44:46 jca 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 <sys/uio.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int res_search_async_run(struct asr_query *, struct asr_result *); +static size_t domcat(const char *, const char *, char *, size_t); + +/* + * Unlike res_query_async(), this function returns a valid packet only if + * h_errno is NETDB_SUCCESS. + */ +struct asr_query * +res_search_async(const char *name, int class, int type, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type); + + ac = _asr_use_resolver(asr); + as = _res_search_async_ctx(name, class, type, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(res_search_async); + +struct asr_query * +_res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac) +{ + struct asr_query *as; + + DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class, + type); + + if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL) + goto err; /* errno set */ + as->as_run = res_search_async_run; + if ((as->as.search.name = strdup(name)) == NULL) + goto err; /* errno set */ + + as->as.search.class = class; + as->as.search.type = type; + + return (as); + err: + if (as) + _asr_async_free(as); + return (NULL); +} + +#define HERRNO_UNSET -2 + +static int +res_search_async_run(struct asr_query *as, struct asr_result *ar) +{ + int r; + char fqdn[MAXDNAME]; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as.search.name[0] == '\0') { + ar->ar_h_errno = NO_DATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + as->as.search.saved_h_errno = HERRNO_UNSET; + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + + case ASR_STATE_NEXT_DOMAIN: + /* + * Reset flags to be able to identify the case in + * STATE_SUBQUERY. + */ + as->as_dom_flags = 0; + + r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn)); + if (r == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + if (r == 0) { + ar->ar_errno = EINVAL; + ar->ar_h_errno = NO_RECOVERY; + ar->ar_datalen = -1; + ar->ar_data = NULL; + async_set_state(as, ASR_STATE_HALT); + break; + } + as->as_subq = _res_query_async_ctx(fqdn, + as->as.search.class, as->as.search.type, as->as_ctx); + if (as->as_subq == NULL) { + ar->ar_errno = errno; + if (errno == EINVAL) + ar->ar_h_errno = NO_RECOVERY; + else + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_datalen = -1; + ar->ar_data = NULL; + 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_h_errno == NETDB_SUCCESS) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* + * The original res_search() does this in the domain search + * loop, but only for ECONNREFUSED. I think we can do better + * because technically if we get an errno, it means + * we couldn't reach any nameserver, so there is no point + * in trying further. + */ + if (ar->ar_errno) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + free(ar->ar_data); + + /* + * The original resolver does something like this. + */ + if (as->as_dom_flags & ASYNC_DOM_NDOTS) + as->as.search.saved_h_errno = ar->ar_h_errno; + + if (as->as_dom_flags & ASYNC_DOM_DOMAIN) { + if (ar->ar_h_errno == NO_DATA) + as->as_flags |= ASYNC_NODATA; + else if (ar->ar_h_errno == TRY_AGAIN) + as->as_flags |= ASYNC_AGAIN; + } + + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + + case ASR_STATE_NOT_FOUND: + + if (as->as.search.saved_h_errno != HERRNO_UNSET) + ar->ar_h_errno = as->as.search.saved_h_errno; + else if (as->as_flags & ASYNC_NODATA) + ar->ar_h_errno = NO_DATA; + else if (as->as_flags & ASYNC_AGAIN) + ar->ar_h_errno = TRY_AGAIN; + /* + * Else, we got the ar_h_errno value set by res_query_async() + * for the last domain. + */ + ar->ar_datalen = -1; + ar->ar_data = NULL; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Concatenate a name and a domain name. The result has no trailing dot. + * Return the resulting string length, or 0 in case of error. + */ +static size_t +domcat(const char *name, const char *domain, char *buf, size_t buflen) +{ + size_t r; + + r = _asr_make_fqdn(name, domain, buf, buflen); + if (r == 0) + return (0); + buf[r - 1] = '\0'; + + return (r - 1); +} + +enum { + DOM_INIT, + DOM_DOMAIN, + DOM_DONE +}; + +/* + * Implement the search domain strategy. + * + * This function works as a generator that constructs complete domains in + * buffer "buf" of size "len" for the given host name "name", according to the + * search rules defined by the resolving context. It is supposed to be called + * multiple times (with the same name) to generate the next possible domain + * name, if any. + * + * It returns -1 if all possibilities have been exhausted, 0 if there was an + * error generating the next name, or the resulting name length. + */ +int +_asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len) +{ + const char *c; + int dots; + + switch (as->as_dom_step) { + + case DOM_INIT: + /* First call */ + + /* + * If "name" is an FQDN, that's the only result and we + * don't try anything else. + */ + if (strlen(name) && name[strlen(name) - 1] == '.') { + DPRINT("asr: iter_domain(\"%s\") fqdn\n", name); + as->as_dom_flags |= ASYNC_DOM_FQDN; + as->as_dom_step = DOM_DONE; + return (domcat(name, NULL, buf, len)); + } + + /* + * Otherwise, we iterate through the specified search domains. + */ + as->as_dom_step = DOM_DOMAIN; + as->as_dom_idx = 0; + + /* + * If "name" as enough dots, use it as-is first, as indicated + * in resolv.conf(5). + */ + dots = 0; + for (c = name; *c; c++) + dots += (*c == '.'); + if (dots >= as->as_ctx->ac_ndots) { + DPRINT("asr: iter_domain(\"%s\") ndots\n", name); + as->as_dom_flags |= ASYNC_DOM_NDOTS; + if (strlcpy(buf, name, len) >= len) + return (0); + return (strlen(buf)); + } + /* Otherwise, starts using the search domains */ + /* FALLTHROUGH */ + + case DOM_DOMAIN: + if (as->as_dom_idx < as->as_ctx->ac_domcount && + (as->as_ctx->ac_options & RES_DNSRCH || ( + as->as_ctx->ac_options & RES_DEFNAMES && + as->as_dom_idx == 0 && + strchr(name, '.') == NULL))) { + DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n", + name, as->as_ctx->ac_dom[as->as_dom_idx]); + as->as_dom_flags |= ASYNC_DOM_DOMAIN; + return (domcat(name, + as->as_ctx->ac_dom[as->as_dom_idx++], buf, len)); + } + + /* No more domain to try. */ + + as->as_dom_step = DOM_DONE; + + /* + * If the name was not tried as an absolute name before, + * do it now. + */ + if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) { + DPRINT("asr: iter_domain(\"%s\") as is\n", name); + as->as_dom_flags |= ASYNC_DOM_ASIS; + if (strlcpy(buf, name, len) >= len) + return (0); + return (strlen(buf)); + } + /* Otherwise, we are done. */ + + case DOM_DONE: + default: + DPRINT("asr: iter_domain(\"%s\") done\n", name); + return (-1); + } +} diff --git a/openbsd-compat/libasr/res_send.c b/openbsd-compat/libasr/res_send.c new file mode 100644 index 00000000..32c94081 --- /dev/null +++ b/openbsd-compat/libasr/res_send.c @@ -0,0 +1,61 @@ +/* $OpenBSD: res_send.c,v 1.8 2014/03/26 18:13:15 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> +#include <stdlib.h> + +int +res_send(const u_char *buf, int buflen, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + errno = EINVAL; + return (-1); + } + + as = res_send_async(buf, buflen, NULL); + if (as == NULL) + return (-1); /* errno set */ + + asr_run_sync(as, &ar); + + if (ar.ar_errno) { + errno = ar.ar_errno; + return (-1); + } + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} diff --git a/openbsd-compat/libasr/res_send_async.c b/openbsd-compat/libasr/res_send_async.c new file mode 100644 index 00000000..7eeeef48 --- /dev/null +++ b/openbsd-compat/libasr/res_send_async.c @@ -0,0 +1,806 @@ +/* $OpenBSD: res_send_async.c,v 1.39 2019/09/28 11:21:07 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 <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <resolv.h> /* for res_random */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +#define OP_QUERY (0) + +static int res_send_async_run(struct asr_query *, struct asr_result *); +static int sockaddr_connect(const struct sockaddr *, int); +static int udp_send(struct asr_query *); +static int udp_recv(struct asr_query *); +static int tcp_write(struct asr_query *); +static int tcp_read(struct asr_query *); +static int validate_packet(struct asr_query *); +static int setup_query(struct asr_query *, const char *, const char *, int, int); +static int ensure_ibuf(struct asr_query *, size_t); +static int iter_ns(struct asr_query *); + +#define AS_NS_SA(p) ((p)->as_ctx->ac_ns[(p)->as.dns.nsidx - 1]) + + +struct asr_query * +res_send_async(const unsigned char *buf, int buflen, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + + DPRINT_PACKET("asr: res_send_async()", buf, buflen); + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_SEND)) == NULL) { + _asr_ctx_unref(ac); + return (NULL); /* errno set */ + } + as->as_run = res_send_async_run; + + as->as_flags |= ASYNC_EXTOBUF; + as->as.dns.obuf = (unsigned char *)buf; + as->as.dns.obuflen = buflen; + as->as.dns.obufsize = buflen; + + _asr_unpack_init(&p, buf, buflen); + _asr_unpack_header(&p, &h); + _asr_unpack_query(&p, &q); + if (p.err) { + errno = EINVAL; + goto err; + } + as->as.dns.reqid = h.id; + as->as.dns.type = q.q_type; + as->as.dns.class = q.q_class; + as->as.dns.dname = strdup(q.q_dname); + if (as->as.dns.dname == NULL) + goto err; /* errno set */ + + _asr_ctx_unref(ac); + return (as); + err: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(res_send_async); + +/* + * Unlike res_query(), this version will actually return the packet + * if it has received a valid one (errno == 0) even if h_errno is + * not NETDB_SUCCESS. So the packet *must* be freed if necessary. + */ +struct asr_query * +res_query_async(const char *name, int class, int type, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + DPRINT("asr: res_query_async(\"%s\", %i, %i)\n", name, class, type); + + ac = _asr_use_resolver(asr); + as = _res_query_async_ctx(name, class, type, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(res_query_async); + +struct asr_query * +_res_query_async_ctx(const char *name, int class, int type, struct asr_ctx *a_ctx) +{ + struct asr_query *as; + + DPRINT("asr: res_query_async_ctx(\"%s\", %i, %i)\n", name, class, type); + + if ((as = _asr_async_new(a_ctx, ASR_SEND)) == NULL) + return (NULL); /* errno set */ + as->as_run = res_send_async_run; + + /* This adds a "." to name if it doesn't already has one. + * That's how res_query() behaves (through res_mkquery"). + */ + if (setup_query(as, name, NULL, class, type) == -1) + goto err; /* errno set */ + + return (as); + + err: + if (as) + _asr_async_free(as); + + return (NULL); +} + +static int +res_send_async_run(struct asr_query *as, struct asr_result *ar) +{ + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as_ctx->ac_nscount == 0) { + ar->ar_errno = ECONNREFUSED; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_NEXT_NS); + break; + + case ASR_STATE_NEXT_NS: + + if (iter_ns(as) == -1) { + ar->ar_errno = ETIMEDOUT; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_ctx->ac_options & RES_USEVC || + as->as.dns.obuflen > PACKETSZ) + async_set_state(as, ASR_STATE_TCP_WRITE); + else + async_set_state(as, ASR_STATE_UDP_SEND); + break; + + case ASR_STATE_UDP_SEND: + + if (udp_send(as) == -1) { + async_set_state(as, ASR_STATE_NEXT_NS); + break; + } + async_set_state(as, ASR_STATE_UDP_RECV); + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + break; + + case ASR_STATE_UDP_RECV: + + if (udp_recv(as) == -1) { + if (errno == ENOMEM) { + ar->ar_errno = errno; + async_set_state(as, ASR_STATE_HALT); + break; + } + if (errno != EOVERFLOW) { + /* Fail or timeout */ + async_set_state(as, ASR_STATE_NEXT_NS); + break; + } + if (as->as_ctx->ac_options & RES_IGNTC) + async_set_state(as, ASR_STATE_PACKET); + else + async_set_state(as, ASR_STATE_TCP_WRITE); + } else + async_set_state(as, ASR_STATE_PACKET); + break; + + case ASR_STATE_TCP_WRITE: + + switch (tcp_write(as)) { + case -1: /* fail or timeout */ + async_set_state(as, ASR_STATE_NEXT_NS); + break; + case 0: + async_set_state(as, ASR_STATE_TCP_READ); + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + case 1: + ar->ar_cond = ASR_WANT_WRITE; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + } + break; + + case ASR_STATE_TCP_READ: + + switch (tcp_read(as)) { + case -1: /* Fail or timeout */ + if (errno == ENOMEM) { + ar->ar_errno = errno; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_NS); + break; + case 0: + async_set_state(as, ASR_STATE_PACKET); + break; + case 1: + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + } + break; + + case ASR_STATE_PACKET: + + memmove(&ar->ar_ns, AS_NS_SA(as), SA_LEN(AS_NS_SA(as))); + ar->ar_datalen = as->as.dns.ibuflen; + ar->ar_data = as->as.dns.ibuf; + as->as.dns.ibuf = NULL; + ar->ar_errno = 0; + ar->ar_rcode = as->as.dns.rcode; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + if (ar->ar_errno) { + ar->ar_h_errno = TRY_AGAIN; + ar->ar_count = 0; + ar->ar_datalen = -1; + ar->ar_data = NULL; + } else if (as->as.dns.ancount) { + ar->ar_h_errno = NETDB_SUCCESS; + ar->ar_count = as->as.dns.ancount; + } else { + ar->ar_count = 0; + switch (as->as.dns.rcode) { + case NXDOMAIN: + ar->ar_h_errno = HOST_NOT_FOUND; + break; + case SERVFAIL: + ar->ar_h_errno = TRY_AGAIN; + break; + case NOERROR: + ar->ar_h_errno = NO_DATA; + break; + default: + ar->ar_h_errno = NO_RECOVERY; + } + } + return (ASYNC_DONE); + + default: + + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +static int +sockaddr_connect(const struct sockaddr *sa, int socktype) +{ + int errno_save, sock, flags; + + if ((sock = socket(sa->sa_family, socktype, 0)) == -1) + goto fail; + + if ((flags = fcntl(sock, F_GETFL, 0)) == -1) + goto fail; + + flags |= O_NONBLOCK; + + if ((flags = fcntl(sock, F_SETFL, flags)) == -1) + goto fail; + + if (connect(sock, sa, SA_LEN(sa)) == -1) { + /* + * In the TCP case, the caller will be asked to poll for + * POLLOUT so that we start writing the packet in tcp_write() + * when the connection is established, or fail there on error. + */ + if (errno == EINPROGRESS) + return (sock); + goto fail; + } + + return (sock); + + fail: + + if (sock != -1) { + errno_save = errno; + close(sock); + errno = errno_save; + } + + return (-1); +} + +/* + * Prepare the DNS packet for the query type "type", class "class" and domain + * name created by the concatenation on "name" and "dom". + * Return 0 on success, set errno and return -1 on error. + */ +static int +setup_query(struct asr_query *as, const char *name, const char *dom, + int class, int type) +{ + struct asr_pack p; + struct asr_dns_header h; + char fqdn[MAXDNAME]; + char dname[MAXDNAME]; + + if (as->as_flags & ASYNC_EXTOBUF) { + errno = EINVAL; + DPRINT("attempting to write in user packet"); + return (-1); + } + + if (_asr_make_fqdn(name, dom, fqdn, sizeof(fqdn)) > sizeof(fqdn)) { + errno = EINVAL; + DPRINT("asr_make_fqdn: name too long\n"); + return (-1); + } + + if (_asr_dname_from_fqdn(fqdn, dname, sizeof(dname)) == -1) { + errno = EINVAL; + DPRINT("asr_dname_from_fqdn: invalid\n"); + return (-1); + } + + if (as->as.dns.obuf == NULL) { + as->as.dns.obufsize = PACKETSZ; + as->as.dns.obuf = malloc(as->as.dns.obufsize); + if (as->as.dns.obuf == NULL) + return (-1); /* errno set */ + } + as->as.dns.obuflen = 0; + + memset(&h, 0, sizeof h); + h.id = res_randomid(); + if (as->as_ctx->ac_options & RES_RECURSE) + h.flags |= RD_MASK; +#ifdef RES_USE_CD + if (as->as_ctx->ac_options & RES_USE_CD) + h.flags |= CD_MASK; +#endif + h.qdcount = 1; + if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + h.arcount = 1; + + _asr_pack_init(&p, as->as.dns.obuf, as->as.dns.obufsize); + _asr_pack_header(&p, &h); + _asr_pack_query(&p, type, class, dname); + if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + _asr_pack_edns0(&p, MAXPACKETSZ, + as->as_ctx->ac_options & RES_USE_DNSSEC); + if (p.err) { + DPRINT("error packing query"); + errno = EINVAL; + return (-1); + } + + /* Remember the parameters. */ + as->as.dns.reqid = h.id; + as->as.dns.type = type; + as->as.dns.class = class; + if (as->as.dns.dname) + free(as->as.dns.dname); + as->as.dns.dname = strdup(dname); + if (as->as.dns.dname == NULL) { + DPRINT("strdup"); + return (-1); /* errno set */ + } + as->as.dns.obuflen = p.offset; + + DPRINT_PACKET("asr_setup_query", as->as.dns.obuf, as->as.dns.obuflen); + + return (0); +} + +/* + * Create a connect UDP socket and send the output packet. + * + * Return 0 on success, or -1 on error (errno set). + */ +static int +udp_send(struct asr_query *as) +{ + ssize_t n; + int save_errno; +#ifdef DEBUG + char buf[256]; +#endif + + DPRINT("asr: [%p] connecting to %s UDP\n", as, + _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf)); + + as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_DGRAM); + if (as->as_fd == -1) + return (-1); /* errno set */ + + n = send(as->as_fd, as->as.dns.obuf, as->as.dns.obuflen, 0); + if (n == -1) { + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (-1); + } + + return (0); +} + +/* + * Try to receive a valid packet from the current UDP socket. + * + * Return 0 if a full packet could be read, or -1 on error (errno set). + */ +static int +udp_recv(struct asr_query *as) +{ + ssize_t n; + int save_errno; + + if (ensure_ibuf(as, MAXPACKETSZ) == -1) { + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (-1); + } + + n = recv(as->as_fd, as->as.dns.ibuf, as->as.dns.ibufsize, 0); + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + if (n == -1) + return (-1); + + as->as.dns.ibuflen = n; + + DPRINT_PACKET("asr_udp_recv()", as->as.dns.ibuf, as->as.dns.ibuflen); + + if (validate_packet(as) == -1) + return (-1); /* errno set */ + + return (0); +} + +/* + * Write the output packet to the TCP socket. + * + * Return 0 when all bytes have been sent, 1 there is no buffer space on the + * socket or it is not connected yet, or -1 on error (errno set). + */ +static int +tcp_write(struct asr_query *as) +{ + struct msghdr msg; + struct iovec iov[2]; + uint16_t len; + ssize_t n; + size_t offset; + int i; +#ifdef DEBUG + char buf[256]; +#endif + + /* First try to connect if not already */ + if (as->as_fd == -1) { + DPRINT("asr: [%p] connecting to %s TCP\n", as, + _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf)); + as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_STREAM); + if (as->as_fd == -1) + return (-1); /* errno set */ +/* + * Some systems (MacOS X) have SO_NOSIGPIPE instead of MSG_NOSIGNAL. + * If neither is available the system is probably broken. We might + * want to detect this at configure time. + */ +#ifdef SO_NOSIGPIPE + i = 1; + if (setsockopt(as->as_fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&i, + sizeof(i)) == -1) + return (-1); /* errno set */ +#endif + as->as.dns.datalen = 0; /* bytes sent */ + return (1); + } + + i = 0; + + /* Prepend de packet length if not sent already. */ + if (as->as.dns.datalen < sizeof(len)) { + offset = 0; + len = htons(as->as.dns.obuflen); + iov[i].iov_base = (char *)(&len) + as->as.dns.datalen; + iov[i].iov_len = sizeof(len) - as->as.dns.datalen; + i++; + } else + offset = as->as.dns.datalen - sizeof(len); + + iov[i].iov_base = as->as.dns.obuf + offset; + iov[i].iov_len = as->as.dns.obuflen - offset; + i++; + + memset(&msg, 0, sizeof msg); + msg.msg_iov = iov; + msg.msg_iovlen = i; + + send_again: +/* See above. */ +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + n = sendmsg(as->as_fd, &msg, MSG_NOSIGNAL); + if (n == -1) { + if (errno == EINTR) + goto send_again; + goto close; /* errno set */ + } + + as->as.dns.datalen += n; + + if (as->as.dns.datalen == as->as.dns.obuflen + sizeof(len)) { + /* All sent. Prepare for TCP read */ + as->as.dns.datalen = 0; + return (0); + } + + /* More data to write */ + return (1); + +close: + close(as->as_fd); + as->as_fd = -1; + return (-1); +} + +/* + * Try to read a valid packet from the current TCP socket. + * + * Return 0 if a full packet could be read, 1 if more data is needed and the + * socket must be read again, or -1 on error (errno set). + */ +static int +tcp_read(struct asr_query *as) +{ + ssize_t n; + size_t offset, len; + char *pos; + int save_errno, nfds; + struct pollfd pfd; + + /* We must read the packet len first */ + if (as->as.dns.datalen < sizeof(as->as.dns.pktlen)) { + + pos = (char *)(&as->as.dns.pktlen) + as->as.dns.datalen; + len = sizeof(as->as.dns.pktlen) - as->as.dns.datalen; + + read_again0: + n = read(as->as_fd, pos, len); + if (n == -1) { + if (errno == EINTR) + goto read_again0; + goto close; /* errno set */ + } + if (n == 0) { + errno = ECONNRESET; + goto close; + } + as->as.dns.datalen += n; + if (as->as.dns.datalen < sizeof(as->as.dns.pktlen)) + return (1); /* need more data */ + + as->as.dns.ibuflen = ntohs(as->as.dns.pktlen); + if (ensure_ibuf(as, as->as.dns.ibuflen) == -1) + goto close; /* errno set */ + + pfd.fd = as->as_fd; + pfd.events = POLLIN; + poll_again: + nfds = poll(&pfd, 1, 0); + if (nfds == -1) { + if (errno == EINTR) + goto poll_again; + goto close; /* errno set */ + } + if (nfds == 0) + return (1); /* no more data available */ + } + + offset = as->as.dns.datalen - sizeof(as->as.dns.pktlen); + pos = as->as.dns.ibuf + offset; + len = as->as.dns.ibuflen - offset; + + read_again: + n = read(as->as_fd, pos, len); + if (n == -1) { + if (errno == EINTR) + goto read_again; + goto close; /* errno set */ + } + if (n == 0) { + errno = ECONNRESET; + goto close; + } + as->as.dns.datalen += n; + + /* See if we got all the advertised bytes. */ + if (as->as.dns.datalen != as->as.dns.ibuflen + sizeof(as->as.dns.pktlen)) + return (1); + + DPRINT_PACKET("asr_tcp_read()", as->as.dns.ibuf, as->as.dns.ibuflen); + + if (validate_packet(as) == -1) + goto close; /* errno set */ + + errno = 0; +close: + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (errno ? -1 : 0); +} + +/* + * Make sure the input buffer is at least "n" bytes long, and allocate or + * extend it if necessary. Return 0 on success, or set errno and return -1. + */ +static int +ensure_ibuf(struct asr_query *as, size_t n) +{ + char *t; + + if (as->as.dns.ibufsize >= n) + return (0); + + t = recallocarray(as->as.dns.ibuf, as->as.dns.ibufsize, n, 1); + if (t == NULL) + return (-1); /* errno set */ + as->as.dns.ibuf = t; + as->as.dns.ibufsize = n; + + return (0); +} + +/* + * Check if the received packet is valid. + * Return 0 on success, or set errno and return -1. + */ +static int +validate_packet(struct asr_query *as) +{ + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int r; + + _asr_unpack_init(&p, as->as.dns.ibuf, as->as.dns.ibuflen); + + _asr_unpack_header(&p, &h); + if (p.err) + goto inval; + + if (h.id != as->as.dns.reqid) { + DPRINT("incorrect reqid\n"); + goto inval; + } + if (h.qdcount != 1) + goto inval; + /* Should be zero, we could allow this */ + if ((h.flags & Z_MASK) != 0) + goto inval; + /* Actually, it depends on the request but we only use OP_QUERY */ + if (OPCODE(h.flags) != OP_QUERY) + goto inval; + /* Must be a response */ + if ((h.flags & QR_MASK) == 0) + goto inval; + + as->as.dns.rcode = RCODE(h.flags); + as->as.dns.ancount = h.ancount; + + _asr_unpack_query(&p, &q); + if (p.err) + goto inval; + + if (q.q_type != as->as.dns.type || + q.q_class != as->as.dns.class || + strcasecmp(q.q_dname, as->as.dns.dname)) { + DPRINT("incorrect type/class/dname '%s' != '%s'\n", + q.q_dname, as->as.dns.dname); + goto inval; + } + + /* Check for truncation */ + if (h.flags & TC_MASK && !(as->as_ctx->ac_options & RES_IGNTC)) { + DPRINT("truncated\n"); + errno = EOVERFLOW; + return (-1); + } + + /* Validate the rest of the packet */ + for (r = h.ancount + h.nscount + h.arcount; r; r--) + _asr_unpack_rr(&p, &rr); + + /* Report any error found when unpacking the RRs. */ + if (p.err) { + DPRINT("unpack: %s\n", strerror(p.err)); + errno = p.err; + return (-1); + } + + if (p.offset != as->as.dns.ibuflen) { + DPRINT("trailing garbage\n"); + errno = EMSGSIZE; + return (-1); + } + + return (0); + + inval: + errno = EINVAL; + return (-1); +} + +/* + * Set the async context nameserver index to the next nameserver, cycling + * over the list until the maximum retry counter is reached. Return 0 on + * success, or -1 if all nameservers were used. + */ +static int +iter_ns(struct asr_query *as) +{ + for (;;) { + if (as->as.dns.nsloop >= as->as_ctx->ac_nsretries) + return (-1); + + as->as.dns.nsidx += 1; + if (as->as.dns.nsidx <= as->as_ctx->ac_nscount) + break; + as->as.dns.nsidx = 0; + as->as.dns.nsloop++; + DPRINT("asr: iter_ns(): cycle %i\n", as->as.dns.nsloop); + } + + as->as_timeout = 1000 * (as->as_ctx->ac_nstimeout << as->as.dns.nsloop); + if (as->as.dns.nsloop > 0) + as->as_timeout /= as->as_ctx->ac_nscount; + if (as->as_timeout < 1000) + as->as_timeout = 1000; + + return (0); +} diff --git a/openbsd-compat/libasr/sethostent.c b/openbsd-compat/libasr/sethostent.c new file mode 100644 index 00000000..61fa3e2f --- /dev/null +++ b/openbsd-compat/libasr/sethostent.c @@ -0,0 +1,36 @@ +/* $OpenBSD: sethostent.c,v 1.2 2018/04/28 15:09:35 schwarze 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 <netdb.h> +#include <stddef.h> + +void +sethostent(int stayopen) +{ +} + +void +endhostent(void) +{ +} + +struct hostent * +gethostent(void) +{ + h_errno = NETDB_INTERNAL; + return NULL; +} diff --git a/openbsd-compat/libasr/thread_private.h b/openbsd-compat/libasr/thread_private.h new file mode 100644 index 00000000..23951975 --- /dev/null +++ b/openbsd-compat/libasr/thread_private.h @@ -0,0 +1,8 @@ +/* + * + */ +#define __is_threaded (0) +#define _THREAD_PRIVATE_MUTEX(x) +#define _THREAD_PRIVATE_MUTEX_LOCK(x) +#define _THREAD_PRIVATE_MUTEX_UNLOCK(x) +#define _THREAD_PRIVATE(a, b, c) (c) |