diff options
author | Eric Faurot <eric@cvs.openbsd.org> | 2012-04-14 09:24:19 +0000 |
---|---|---|
committer | Eric Faurot <eric@cvs.openbsd.org> | 2012-04-14 09:24:19 +0000 |
commit | 852a7ed231a6296007177e8f5df9ec3978d4dab8 (patch) | |
tree | da2dada44712de6b50d559bd5cf9b3869eba3746 /lib | |
parent | b24bb61092bfc78fc1d60744dd936d4f10e3de57 (diff) |
Import asr, an experimental async resolver implementation.
The idea is to eventually replace the existing resolver with
something better. Time to start working on it in tree.
ok deraadt@
Diffstat (limited to 'lib')
-rw-r--r-- | lib/libc/asr/Makefile.inc | 34 | ||||
-rw-r--r-- | lib/libc/asr/asr.c | 1102 | ||||
-rw-r--r-- | lib/libc/asr/asr.h | 106 | ||||
-rw-r--r-- | lib/libc/asr/asr_debug.c | 569 | ||||
-rw-r--r-- | lib/libc/asr/asr_private.h | 372 | ||||
-rw-r--r-- | lib/libc/asr/asr_resolver.c | 457 | ||||
-rw-r--r-- | lib/libc/asr/asr_utils.c | 450 | ||||
-rw-r--r-- | lib/libc/asr/async_resolver.3 | 358 | ||||
-rw-r--r-- | lib/libc/asr/getaddrinfo_async.c | 500 | ||||
-rw-r--r-- | lib/libc/asr/gethostnamadr_async.c | 592 | ||||
-rw-r--r-- | lib/libc/asr/getnameinfo_async.c | 261 | ||||
-rw-r--r-- | lib/libc/asr/getnetnamadr_async.c | 418 | ||||
-rw-r--r-- | lib/libc/asr/getrrsetbyname_async.c | 590 | ||||
-rw-r--r-- | lib/libc/asr/hostaddr_async.c | 342 | ||||
-rw-r--r-- | lib/libc/asr/res_search_async.c | 224 | ||||
-rw-r--r-- | lib/libc/asr/res_send_async.c | 809 |
16 files changed, 7184 insertions, 0 deletions
diff --git a/lib/libc/asr/Makefile.inc b/lib/libc/asr/Makefile.inc new file mode 100644 index 00000000000..f875dcf91b2 --- /dev/null +++ b/lib/libc/asr/Makefile.inc @@ -0,0 +1,34 @@ +# $OpenBSD: Makefile.inc,v 1.1 2012/04/14 09:24:18 eric Exp $ + +# asr sources +.PATH: ${LIBCSRCDIR}/asr + +CFLAGS+=-DDEBUG +SRCS+= asr.c asr_debug.c asr_utils.c \ + res_send_async.c res_search_async.c getrrsetbyname_async.c \ + gethostnamadr_async.c getnetnamadr_async.c \ + hostaddr_async.c getaddrinfo_async.c getnameinfo_async.c + +# resolver replacement. comment out to disable. +SRCS+= asr_resolver.c + +MLINKS+ = \ + async_resolver.3 async_resolver_done.3 \ + async_resolver.3 async_run.3 \ + async_resolver.3 async_run_sync.3 \ + async_resolver.3 async_abort.3 \ + async_resolver.3 res_send_async.3 \ + async_resolver.3 res_query_async.3 \ + async_resolver.3 res_search_async.3 \ + async_resolver.3 getrrsetbyname_async.3 \ + async_resolver.3 gethostbyname_async.3 \ + async_resolver.3 gethostbyname2_async.3 \ + async_resolver.3 gethostbyaddr_async.3 \ + async_resolver.3 freehostent.3 \ + async_resolver.3 getnetbyname_async.3 \ + async_resolver.3 getnetbyaddr_async.3 \ + async_resolver.3 freenetent.3 \ + async_resolver.3 getaddrinfo_async.3 \ + async_resolver.3 getnameinfo_async.3 + +.include <bsd.lib.mk> diff --git a/lib/libc/asr/asr.c b/lib/libc/asr/asr.c new file mode 100644 index 00000000000..0980a739ba8 --- /dev/null +++ b/lib/libc/asr/asr.c @@ -0,0 +1,1102 @@ +/* $OpenBSD: asr.c,v 1.1 2012/04/14 09:24:18 eric 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 <sys/types.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <resolv.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "asr_private.h" + +#define DEFAULT_CONFFILE "/etc/resolv.conf" +#define DEFAULT_HOSTFILE "/etc/hosts" +#define DEFAULT_CONF "lookup bind file\nnameserver 127.0.0.1\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(const char*, int(*)(char**, int, struct asr_ctx*), + struct asr_ctx *); +static int asr_parse_nameserver(struct sockaddr *, const char *); +static char *asr_hostalias(const char *, char *, size_t); +static int asr_ndots(const char *); +static void asr_ctx_envopts(struct asr_ctx *); +static int pass0(char **, int, struct asr_ctx *); + +static struct asr * _default_resolver = NULL; + +/* Allocate and configure an async "resolver". */ +struct asr * +async_resolver(const char *conf) +{ + static int init = 0; + struct asr *asr; + +#ifdef DEBUG + if (init == 0) { + if (getenv("ASR_DEBUG")) + asr_debug = 1; + init = 1; + } +#endif + if ((asr = calloc(1, sizeof(*asr))) == NULL) + goto fail; + + /* If not setuid/setgid, allow to use an alternate config. */ + if (conf == NULL && !issetugid()) + conf = getenv("ASR_CONFIG"); + + if (conf == NULL) + conf = DEFAULT_CONFFILE; + + if (conf[0] == '!') { + /* Use the rest of the string as config file */ + if ((asr->a_ctx = asr_ctx_create()) == NULL) + goto fail; + if (asr_ctx_from_string(asr->a_ctx, conf + 1) == -1) + goto fail; + } else { + /* Use the given config file */ + asr->a_path = strdup(conf); + 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(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 +async_resolver_done(struct asr *asr) +{ + if (asr == NULL) { + if (_default_resolver == NULL) + return; + asr = _default_resolver; + _default_resolver = NULL; + } + + asr_ctx_unref(asr->a_ctx); + if (asr->a_path) + free(asr->a_path); + free(asr); +} + +/* + * Cancel an async query. + */ +void +async_abort(struct async *as) +{ + async_free(as); +} + +/* + * Resume the "as" async query resolution. Return one of ASYNC_COND, + * ASYNC_YIELD or ASYNC_DONE and put query-specific return values in + * the user-allocated memory at "ar". + */ +int +async_run(struct async *as, struct async_res *ar) +{ + int r; + +#ifdef DEBUG + asr_printf("asr: async_run(%p, %p) %s ctx=[%p]\n", + as, ar, asr_querystr(as->as_type), as->as_ctx); +#endif + r = as->as_run(as, ar); + +#ifdef DEBUG + if (asr_debug) { + asr_printf("asr: async_run(%p, %p) -> %s", as, ar, + asr_transitionstr(r)); + if (r == ASYNC_COND) + asr_printf(" fd=%i timeout=%i\n", + ar->ar_fd, ar->ar_timeout); + else + asr_printf("\n"); + fflush(stderr); + } +#endif + if (r == ASYNC_DONE) + async_free(as); + + return (r); +} + +/* + * Same as above, but run in a loop that handles the fd conditions result. + */ +int +async_run_sync(struct async *as, struct async_res *ar) +{ + struct pollfd fds[1]; + int r; + + while((r = async_run(as, ar)) == ASYNC_COND) { + fds[0].fd = ar->ar_fd; + fds[0].events = (ar->ar_cond == ASYNC_READ) ? POLLIN : POLLOUT; + again: + r = poll(fds, 1, ar->ar_timeout); + if (r == -1 && errno == EINTR) + goto again; + if (r == -1) /* XXX Is it possible? and what to do if so? */ + err(1, "poll"); + } + + return (r); +} + +/* + * Create a new async request of the given "type" on the async context "ac". + * Take a reference on it so it does not gets deleted while the async query + * is running. + */ +struct async * +async_new(struct asr_ctx *ac, int type) +{ + struct async *as; +#ifdef DEBUG + asr_printf("asr: async_new(ctx=%p) type=%i refcount=%i\n", + ac, type, ac->ac_refcount); +#endif + if ((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 +async_free(struct async *as) +{ +#ifdef DEBUG + asr_printf("asr: async_free(%p)\n", as); +#endif + switch(as->as_type) { + case ASR_SEND: + if (as->as_fd != -1) + close(as->as_fd); + if (as->as.dns.obuf && !(as->as.dns.flags & ASYNC_EXTOBUF)) + free (as->as.dns.obuf); + if (as->as.dns.ibuf && !(as->as.dns.flags & ASYNC_EXTIBUF)) + free (as->as.dns.ibuf); + if (as->as.dns.dname) + free(as->as.dns.dname); + break; + + case ASR_SEARCH: + if (as->as.search.subq) + async_free(as->as.search.subq); + if (as->as.search.name) + free(as->as.search.name); + break; + + case ASR_GETRRSETBYNAME: + if (as->as.rrset.subq) + async_free(as->as.rrset.subq); + if (as->as.rrset.name) + free(as->as.rrset.name); + break; + + case ASR_GETHOSTBYNAME: + case ASR_GETHOSTBYADDR: + if (as->as.hostnamadr.subq) + async_free(as->as.hostnamadr.subq); + if (as->as.hostnamadr.name) + free(as->as.hostnamadr.name); + if (as->as.hostnamadr.dname) + free(as->as.hostnamadr.dname); + break; + + case ASR_GETNETBYNAME: + case ASR_GETNETBYADDR: + if (as->as.netnamadr.subq) + async_free(as->as.netnamadr.subq); + if (as->as.netnamadr.name) + free(as->as.netnamadr.name); + break; + + case ASR_GETADDRINFO: + if (as->as.ai.subq) + async_free(as->as.ai.subq); + 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); + break; + + case ASR_GETNAMEINFO: + if (as->as.ni.subq) + async_free(as->as.ni.subq); + break; + + case ASR_HOSTADDR: + if (as->as.host.name) + free(as->as.host.name); + if (as->as.host.subq) + async_free(as->as.host.subq); + if (as->as.host.pkt) + free(as->as.host.pkt); + if (as->as.host.file) + fclose(as->as.host.file); + 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 explicitely dropped when done + * using this context. + */ +struct asr_ctx * +asr_use_resolver(struct asr *asr) +{ + if (asr == NULL) { + /* We want the use the global resolver. */ + + /* _THREAD_PRIVATE_MUTEX_LOCK(_asr_mutex); */ + if (_default_resolver != NULL) + asr_check_reload(asr); + else + _default_resolver = async_resolver(NULL); + asr = _default_resolver; + /* _THREAD_PRIVATE_MUTEX_UNLOCK(_asr_mutex); */ + } + + asr_check_reload(asr); + asr_ctx_ref(asr->a_ctx); + return (asr->a_ctx); +} + +static void +asr_ctx_ref(struct asr_ctx *ac) +{ +#ifdef DEBUG + asr_printf("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", + ac, ac->ac_refcount); +#endif + 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) +{ +#ifdef DEBUG + asr_printf("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", + ac, ac->ac_refcount); +#endif + 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 < ac->ac_nscount; i++) + free(ac->ac_ns[i]); + for(i = 0; i < ac->ac_domcount; 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 stat st; + struct asr_ctx *ac; + struct timespec tp; + + if (asr->a_path == NULL) + return; + + if (clock_gettime(CLOCK_MONOTONIC, &tp) == -1) + return; + + if ((tp.tv_sec - asr->a_rtime) < RELOAD_DELAY) + return; + asr->a_rtime = tp.tv_sec; + +#ifdef DEBUG + asr_printf("asr: checking for update of \"%s\"\n", asr->a_path); +#endif + + if (stat(asr->a_path, &st) == -1 || + asr->a_mtime == st.st_mtime || + (ac = asr_ctx_create()) == NULL) + return; + asr->a_mtime = st.st_mtime; + +#ifdef DEBUG + asr_printf("asr: reloading config file\n"); +#endif + + if (asr_ctx_from_file(ac, asr->a_path) == -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) { + strlcpy(buf, domain, buflen); + } else if (name[len - 1] != '.') { + if (domain[0] == '.') + domain += 1; + strlcpy(buf, name, buflen); + strlcat(buf, ".", buflen); + strlcat(buf, domain, buflen); + } else { + strlcpy(buf, name, buflen); + } + + return (strlen(buf)); +} + +/* + * Concatenate a name and a domain name. The result has no trailing dot. + */ +size_t +asr_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); +} + +/* + * 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; + ac->ac_family[0] = AF_INET; + ac->ac_family[1] = AF_INET6; + ac->ac_family[2] = -1; + + ac->ac_hostfile = DEFAULT_HOSTFILE; + + ac->ac_nscount = 0; + ac->ac_nstimeout = 1000; + ac->ac_nsretries = 3; + + return (ac); +} + +/* + * 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); +} + +/* + * Pass on a split config line. + */ +static int +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 (0); + if (n != 2) + return (0); + if (asr_parse_nameserver((struct sockaddr*)&ss, tok[1])) + return (0); + if ((ac->ac_ns[ac->ac_nscount] = calloc(1, ss.ss_len)) == NULL) + return (0); + memmove(ac->ac_ns[ac->ac_nscount], &ss, ss.ss_len); + ac->ac_nscount += 1; + + } else if (!strcmp(tok[0], "domain")) { + if (n != 2) + return (0); + if (ac->ac_domain) + return (0); + ac->ac_domain = strdup(tok[1]); + + } else if (!strcmp(tok[0], "lookup")) { + /* ignore the line if we already set lookup */ + if (ac->ac_dbcount != 0) + return (0); + if (n - 1 > ASR_MAXDB) + return (0); + /* 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 (0); + for(i = 1; i < n; i++, ac->ac_dbcount++) { + if (!strcmp(tok[i], "yp")) { + ac->ac_db[i-1] = ASR_DB_YP; + } else if (!strcmp(tok[i], "bind")) { + ac->ac_db[i-1] = ASR_DB_DNS; + } else if (!strcmp(tok[i], "file")) { + ac->ac_db[i-1] = ASR_DB_FILE; + } else { + /* ignore the line */ + ac->ac_dbcount = 0; + return (0); + } + } + } else if (!strcmp(tok[0], "search")) { + /* resolv.conf says the last line wins */ + for(i = 0; i < ac->ac_domcount; i++) + free(ac->ac_dom[i]); + 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 (0); + for (i = 1; i < n; i++) + if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6")) + return (0); + 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 ((!strncmp(tok[i], "ndots:", 6))) { + e = NULL; + d = strtonum(tok[i] + 6, 1, 16, &e); + if (e == NULL) + ac->ac_ndots = d; + } + } + } + + return (0); +} + +/* + * 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(str, pass0, ac); + + if (ac->ac_dbcount == 0) { + /* No lookup directive */ + asr_ctx_parse(DEFAULT_LOOKUP, pass0, ac); + } + + if (ac->ac_nscount == 0) + asr_ctx_parse("nameserver 127.0.0.1", pass0, ac); + + 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, "r"); + if (cf == NULL) + return (-1); + + r = fread(buf, 1, sizeof buf - 1, cf); + if (feof(cf) == 0) { +#ifdef DEBUG + asr_printf("asr: config file too long: \"%s\"\n", path); +#endif + r = -1; + } + fclose(cf); + if (r == -1) + return (-1); + buf[r] = '\0'; + + return asr_ctx_from_string(ac, buf); +} + +/* + * Parse a configuration string. Lines are read one by one, comments are + * stripped and the remaining line is split into tokens which are passed + * to the "cb" callback function. Parsing stops if the callback returns + * non-zero. + */ +static int +asr_ctx_parse(const char *str, int (*cb)(char**, int, struct asr_ctx*), + struct asr_ctx *ac) +{ + size_t len; + const char *line; + char buf[1024]; + char *tok[10], **tp, *cp; + 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'; + for(cp = buf, tp = tok, ntok = 0; + tp < &tok[10] && (*tp = strsep(&cp, " \t")) != NULL; ) + if (**tp != '\0') { + tp++; + ntok++; + } + *tp = NULL; + + if (tok[0] == NULL) + continue; + + if (cb(tok, ntok, ac)) + break; + } + + return (0); +} + +/* + * Check for environment variables altering the configuration as described + * in resolv.conf(5). Altough 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); + s = strlcat(buf, "\n", sizeof buf); + if (s < sizeof buf) + asr_ctx_parse(buf, pass0, ac); + } + + 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(buf, pass0, ac); + } +} + +/* + * Parse a resolv.conf(5) nameserver string into a sockaddr. + */ +static int +asr_parse_nameserver(struct sockaddr *sa, const char *s) +{ + const char *estr; + char buf[256]; + char *port = NULL; + in_port_t portno = 53; + + if (*s == '[') { + strlcpy(buf, s + 1, sizeof buf); + s = buf; + port = strchr(buf, ']'); + if (port == NULL) + return (-1); + *port++ = '\0'; + if (*port != ':') + return (-1); + port++; + } + + if (port) { + portno = strtonum(port, 1, USHRT_MAX, &estr); + if (estr) + return (-1); + } + + if (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) +{ + size_t len; + char *buf, *cp, **tp; + int ntok; + + again: + if ((buf = fgetln(file, &len)) == NULL) + return (-1); + + if (buf[len - 1] == '\n') + len--; + + buf[len] = '\0'; + buf[strcspn(buf, "#")] = '\0'; + for(cp = buf, tp = tokens, ntok = 0; + ntok < ntoken && (*tp = strsep(&cp, " \t")) != NULL;) + if (**tp != '\0') { + tp++; + ntok++; + } + *tp = NULL; + if (tokens[0] == NULL) + 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 async *as) +{ + if (as->as_db_idx >= as->as_ctx->ac_dbcount) { +#if DEBUG + asr_printf("asr_iter_db: done\n"); +#endif + return (-1); + } + + as->as_db_idx += 1; + as->as_ns_idx = 0; +#if DEBUG + asr_printf("asr_iter_db: %i\n", as->as_db_idx); +#endif + return (0); +} + +/* + * Set the async context nameserver index to the next nameserver of the + * currently used DB (assuming it is DNS), cycling over the list until the + * maximum retry counter is reached. Return 0 on success, or -1 if all + * nameservers were used. + */ +int +asr_iter_ns(struct async *as) +{ + for (;;) { + if (as->as_ns_cycles >= as->as_ctx->ac_nsretries) + return (-1); + + as->as_ns_idx += 1; + if (as->as_ns_idx <= as->as_ctx->ac_nscount) + break; + as->as_ns_idx = 0; + as->as_ns_cycles++; +#if DEBUG + asr_printf("asr: asr_iter_ns(): cycle %i\n", as->as_ns_cycles); +#endif + } + + return (0); +} + +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 0 if it could generate a new domain name, or -1 when all + * possibilites have been exhausted. + */ +int +asr_iter_domain(struct async *as, const char *name, char * buf, size_t len) +{ + char *alias; + + 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] == '.') { +#if DEBUG + asr_printf("asr: asr_iter_domain(\"%s\") fqdn\n", name); +#endif + as->as_dom_flags |= ASYNC_DOM_FQDN; + as->as_dom_step = DOM_DONE; + return (asr_domcat(name, NULL, buf, len)); + } + + /* + * If "name" has no dots, it might be an alias. If so, + * That's also the only result. + */ + if ((as->as_ctx->ac_options & RES_NOALIASES) == 0 && + asr_ndots(name) == 0 && + (alias = asr_hostalias(name, buf, len)) != NULL) { +#if DEBUG + asr_printf("asr: asr_iter_domain(\"%s\") is alias " + "\"%s\"\n", name, alias); +#endif + as->as_dom_flags |= ASYNC_DOM_HOSTALIAS; + as->as_dom_step = DOM_DONE; + return (asr_domcat(alias, 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). + */ + if ((asr_ndots(name)) >= as->as_ctx->ac_ndots) { +#ifdef DEBUG + asr_printf("asr: asr_iter_domain(\"%s\") ndots\n", + name); +#endif + as->as_dom_flags |= ASYNC_DOM_NDOTS; + strlcpy(buf, name, len); + return (0); + } + /* Otherwise, starts using the search domains */ + /* FALLTHROUGH */ + + case DOM_DOMAIN: + if (as->as_dom_idx < as->as_ctx->ac_domcount) { +#ifdef DEBUG + asr_printf("asr: asr_iter_domain(\"%s\") " + "domain \"%s\"\n", name, + as->as_ctx->ac_dom[as->as_dom_idx]); +#endif + as->as_dom_flags |= ASYNC_DOM_DOMAIN; + return (asr_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)) { +#ifdef DEBUG + asr_printf("asr: asr_iter_domain(\"%s\") as is\n", + name); +#endif + as->as_dom_flags |= ASYNC_DOM_ASIS; + strlcpy(buf, name, len); + return (0); + } + /* Otherwise, we are done. */ + + case DOM_DONE: + default: +#ifdef DEBUG + asr_printf("asr: asr_iter_domain(\"%s\") done\n", name); +#endif + return (-1); + } +} + +/* + * Check if the hostname "name" is a user-defined alias as per hostname(7). + * If so, copies the result in the buffer "abuf" of size "abufsz" and + * return "abuf". Otherwise return NULL. + */ +static char * +asr_hostalias(const char *name, char *abuf, size_t abufsz) +{ + FILE *fp; + size_t len; + char *file, *buf, *cp, **tp, *tokens[2]; + int ntok; + + file = getenv("HOSTALIASES"); + if (file == NULL || issetugid() != 0 || (fp = fopen(file, "r")) == NULL) + return (NULL); + +#ifdef DEBUG + asr_printf("asr: looking up aliases in \"%s\"\n", file); +#endif + + while ((buf = fgetln(fp, &len)) != NULL) { + if (buf[len - 1] == '\n') + len--; + buf[len] = '\0'; + for(cp = buf, tp = tokens, ntok = 0; + ntok < 2 && (*tp = strsep(&cp, " \t")) != NULL; ) + if (**tp != '\0') { + tp++; + ntok++; + } + if (ntok != 2) + continue; + if (!strcasecmp(tokens[0], name)) { + if (strlcpy(abuf, tokens[1], abufsz) > abufsz) + continue; +#ifdef DEBUG + asr_printf("asr: found alias \"%s\"\n", abuf); +#endif + fclose(fp); + return (abuf); + } + } + + fclose(fp); + return (NULL); +} diff --git a/lib/libc/asr/asr.h b/lib/libc/asr/asr.h new file mode 100644 index 00000000000..2a562092326 --- /dev/null +++ b/lib/libc/asr/asr.h @@ -0,0 +1,106 @@ +/* $OpenBSD: asr.h,v 1.1 2012/04/14 09:24:18 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 <netdb.h> +#include <netinet/in.h> + +/* + * This part is the generic API for the async mechanism. It could be useful + * beyond the resolver. + */ + +/* Return values for async_run() */ +#define ASYNC_COND 0 /* wait for fd condition */ +#define ASYNC_YIELD 1 /* partial result */ +#define ASYNC_DONE 2 /* done */ + +/* Expected fd conditions */ +#define ASYNC_READ 1 +#define ASYNC_WRITE 2 + +/* This opaque structure holds an async query state. */ +struct async; + +/* + * This is the structure through which async_run() returns async + * results to the caller. + */ +struct async_res { + int ar_cond; + int ar_fd; + int ar_timeout; + + int ar_errno; + int ar_h_errno; + int ar_gai_errno; + int ar_rrset_errno; + + int ar_rcode; + void *ar_data; + int ar_datalen; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } ar_sa; + char *ar_cname; + int ar_count; + + struct addrinfo *ar_addrinfo; + struct rrsetinfo *ar_rrsetinfo; + struct hostent *ar_hostent; + struct netent *ar_netent; +}; + +int async_run(struct async *, struct async_res *); +int async_run_sync(struct async *, struct async_res *); +void async_abort(struct async *); + +/* This opaque structure holds an async resolver context. */ +struct asr; + +struct asr *async_resolver(const char*); +void async_resolver_done(struct asr*); + +/* Async version of the resolver API */ + +struct async *res_send_async(const unsigned char *, int, unsigned char *, int, + struct asr *); +struct async *res_query_async(const char *, int, int, unsigned char *, int, + struct asr *); +struct async *res_search_async(const char *, int, int, unsigned char *, int, + struct asr *); + +struct async *getrrsetbyname_async(const char *, unsigned int, unsigned int, + unsigned int, struct asr *); + +struct async *gethostbyname_async(const char *, struct asr *); +struct async *gethostbyname2_async(const char *, int, struct asr *); +struct async *gethostbyaddr_async(const void *, socklen_t, int, struct asr *); +void freehostent(struct hostent *); + +struct async *getnetbyname_async(const char *, struct asr *); +struct async *getnetbyaddr_async(in_addr_t, int, struct asr *); +void freenetent(struct netent *); + +struct async *getaddrinfo_async(const char *, const char *, + const struct addrinfo *, struct asr *); +struct async *getnameinfo_async(const struct sockaddr *, socklen_t, char *, + size_t, char *, size_t, int, struct asr *); diff --git a/lib/libc/asr/asr_debug.c b/lib/libc/asr/asr_debug.c new file mode 100644 index 00000000000..1305633c352 --- /dev/null +++ b/lib/libc/asr/asr_debug.c @@ -0,0 +1,569 @@ +/* $OpenBSD: asr_debug.c,v 1.1 2012/04/14 09:24:18 eric 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 <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <inttypes.h> +#include <resolv.h> +#include <string.h> +#include <stdarg.h> + +#include "asr.h" +#include "asr_private.h" + +static void asr_vdebug(const char *, va_list); + +static char *print_dname(const char *, char *, size_t); +static char *print_host(const struct sockaddr *, char *, size_t); + +static const char *typetostr(uint16_t); +static const char *classtostr(uint16_t); +static const char *rcodetostr(uint16_t); + +static const char *inet6_ntoa(struct in6_addr); + + +#define OPCODE_SHIFT 11 +#define Z_SHIFT 4 + +struct keyval { + const char *key; + uint16_t value; +}; + +static struct keyval kv_class[] = { + { "IN", C_IN }, + { "CHAOS", C_CHAOS }, + { "HS", C_HS }, + { "ANY", C_ANY }, + { NULL, 0 }, +}; + +static struct keyval kv_type[] = { + { "A", T_A }, + { "NS", T_NS }, + { "MD", T_MD }, + { "MF", T_MF }, + { "CNAME", T_CNAME }, + { "SOA", T_SOA }, + { "MB", T_MB }, + { "MG", T_MG }, + { "MR", T_MR }, + { "NULL", T_NULL }, + { "WKS", T_WKS }, + { "PTR", T_PTR }, + { "HINFO", T_HINFO }, + { "MINFO", T_MINFO }, + { "MX", T_MX }, + { "TXT", T_TXT }, + + { "AAAA", T_AAAA }, + + { "AXFR", T_AXFR }, + { "MAILB", T_MAILB }, + { "MAILA", T_MAILA }, + { "ANY", T_ANY }, + { NULL, 0 }, +}; + +static struct keyval kv_rcode[] = { + { "NOERROR", NOERROR }, + { "FORMERR", FORMERR }, + { "SERVFAIL", SERVFAIL }, + { "NXDOMAIN", NXDOMAIN }, + { "NOTIMP", NOTIMP }, + { "REFUSED", REFUSED }, + { NULL, 0 }, +}; + +static const char * +typetostr(uint16_t v) +{ + static char buf[16]; + size_t i; + + for(i = 0; kv_type[i].key; i++) + if (kv_type[i].value == v) + return (kv_type[i].key); + + snprintf(buf, sizeof buf, "%"PRIu16"?", v); + + return (buf); +} + +static const char * +classtostr(uint16_t v) +{ + static char buf[16]; + size_t i; + + for(i = 0; kv_class[i].key; i++) + if (kv_class[i].value == v) + return (kv_class[i].key); + + snprintf(buf, sizeof buf, "%"PRIu16"?", v); + + return (buf); +} + +static const char * +rcodetostr(uint16_t v) +{ + static char buf[16]; + size_t i; + + for(i = 0; kv_rcode[i].key; i++) + if (kv_rcode[i].value == v) + return (kv_rcode[i].key); + + snprintf(buf, sizeof buf, "%"PRIu16"?", v); + + return (buf); +} + +static const char * +inet6_ntoa(struct in6_addr a) +{ + static char buf[256]; + struct sockaddr_in6 si; + + si.sin6_len = sizeof(si); + si.sin6_family = PF_INET6; + si.sin6_addr = a; + + return print_host((struct sockaddr*)&si, buf, sizeof buf); +} + +static char* +print_rr(struct 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, + classtostr(rr->rr_class), + typetostr(rr->rr_type)); + if (r == -1) { + 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, "%"PRIu32" %s", + 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 %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32, + print_dname(rr->rr.soa.rname, tmp, sizeof tmp), + print_dname(rr->rr.soa.mname, tmp2, sizeof tmp2), + rr->rr.soa.serial, + rr->rr.soa.refresh, + rr->rr.soa.retry, + rr->rr.soa.expire, + rr->rr.soa.minimum); + break; + case T_A: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntoa(rr->rr.in_a.addr)); + break; + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet6_ntoa(rr->rr.in_aaaa.addr6)); + break; + default: + other: + snprintf(buf, max, "(rdlen=%"PRIu16 ")", rr->rr.other.rdlen); + break; + } + + return (res); +} + +static char* +print_query(struct query *q, char *buf, size_t max) +{ + char b[256]; + + snprintf(buf, max, "%s %s %s", + print_dname(q->q_dname, b, sizeof b), + classtostr(q->q_class), typetostr(q->q_type)); + + return (buf); +} + +static char* +print_dname(const char *_dname, char *buf, size_t max) +{ + return asr_strdname(_dname, buf, max); +} + +static char* +print_header(struct header *h, char *buf, size_t max, int noid) +{ + snprintf(buf, max, + "id:0x%04x %s op:%i %s %s %s %s z:%i r:%s qd:%i an:%i ns:%i ar:%i", + noid ? 0 : ((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) >> Z_SHIFT), + rcodetostr(RCODE(h->flags)), + h->qdcount, h->ancount, h->nscount, h->arcount); + + return buf; +} + +static char * +print_host(const struct sockaddr *sa, char *buf, size_t len) +{ + int e; + + if ((e = getnameinfo(sa, sa->sa_len, + buf, len, NULL, 0, NI_NUMERICHOST)) != 0) { + buf[0] = '\0'; + return (NULL); + } + return (buf); +} + +char * +print_addr(const struct sockaddr *sa, char *buf, size_t len) +{ + char h[256]; + + print_host(sa, h, sizeof h); + + switch (sa->sa_family) { + case AF_INET: + snprintf(buf, len, "%s:%i", h, + ntohs(((struct sockaddr_in*)(sa))->sin_port)); + break; + case AF_INET6: + snprintf(buf, len, "[%s]:%i", h, + ntohs(((struct sockaddr_in6*)(sa))->sin6_port)); + break; + default: + snprintf(buf, len, "?"); + break; + } + + return (buf); +} + +struct kv { int code; const char *name; }; + +static const char* kvlookup(struct kv *, int); + +int asr_debug = 0; + +void +asr_dump(struct asr *a) +{ + char buf[256]; + int i; + struct asr_ctx *ac; + unsigned int options; + + ac = a->a_ctx; + + asr_printf("--------- ASR CONFIG ---------------\n"); + if (a->a_path) + asr_printf("CONF FILE \"%s\"\n", a->a_path); + else + asr_printf("STATIC CONF\n"); + asr_printf("DOMAIN \"%s\"\n", ac->ac_domain); + asr_printf("SEARCH\n"); + for(i = 0; i < ac->ac_domcount; i++) + asr_printf(" \"%s\"\n", ac->ac_dom[i]); + asr_printf("OPTIONS\n"); + asr_printf(" options:"); + options = ac->ac_options; + if (options & RES_INIT) { + asr_printf(" INIT"); options &= ~RES_INIT; + } + if (options & RES_DEBUG) { + asr_printf(" DEBUG"); options &= ~RES_DEBUG; + } + if (options & RES_USEVC) { + asr_printf(" USEVC"); options &= ~RES_USEVC; + } + if (options & RES_IGNTC) { + asr_printf(" IGNTC"); options &= ~RES_IGNTC; + } + if (options & RES_RECURSE) { + asr_printf(" RECURSE"); options &= ~RES_RECURSE; + } + if (options & RES_DEFNAMES) { + asr_printf(" DEFNAMES"); options &= ~RES_DEFNAMES; + } + if (options & RES_STAYOPEN) { + asr_printf(" STAYOPEN"); options &= ~RES_STAYOPEN; + } + if (options & RES_DNSRCH) { + asr_printf(" DNSRCH"); options &= ~RES_DNSRCH; + } + if (options & RES_NOALIASES) { + asr_printf(" NOALIASES"); options &= ~RES_NOALIASES; + } + if (options & RES_USE_EDNS0) { + asr_printf(" USE_EDNS0"); options &= ~RES_USE_EDNS0; + } + if (options & RES_USE_DNSSEC) { + asr_printf(" USE_DNSSEC"); options &= ~RES_USE_DNSSEC; + } + if (options) + asr_printf("0x%08x\n", options); + asr_printf("\n", ac->ac_options); + + asr_printf(" ndots: %i\n", ac->ac_ndots); + asr_printf(" family:"); + for(i = 0; ac->ac_family[i] != -1; i++) + asr_printf(" %s", (ac->ac_family[i] == AF_INET) ? "inet" : "inet6"); + asr_printf("\n"); + asr_printf("NAMESERVERS timeout=%i retry=%i\n", + ac->ac_nstimeout, + ac->ac_nsretries); + for(i = 0; i < ac->ac_nscount; i++) + asr_printf(" %s\n", print_addr(ac->ac_ns[i], buf, sizeof buf)); + asr_printf("HOSTFILE %s\n", ac->ac_hostfile); + asr_printf("LOOKUP"); + for(i = 0; i < ac->ac_dbcount; i++) { + switch (ac->ac_db[i]) { + case ASR_DB_FILE: + asr_printf(" file"); + break; + case ASR_DB_DNS: + asr_printf(" dns"); + break; + case ASR_DB_YP: + asr_printf(" yp"); + break; + default: + asr_printf(" ?%i", ac->ac_db[i]); + } + } + asr_printf("\n------------------------------------\n"); +} + +static const char * +kvlookup(struct kv *kv, int code) +{ + while (kv->name) { + if (kv->code == code) + return (kv->name); + kv++; + } + return "???"; +} + +struct kv kv_query_type[] = { + { ASR_SEND, "ASR_SEND" }, + { ASR_SEARCH, "ASR_SEARCH" }, + { ASR_GETRRSETBYNAME, "ASR_GETRRSETBYNAME" }, + { ASR_GETHOSTBYNAME, "ASR_GETHOSTBYNAME" }, + { ASR_GETHOSTBYADDR, "ASR_GETHOSTBYADDR" }, + { ASR_GETNETBYNAME, "ASR_GETNETBYNAME" }, + { ASR_GETNETBYADDR, "ASR_GETNETBYADDR" }, + { ASR_GETADDRINFO, "ASR_GETADDRINFO" }, + { ASR_GETNAMEINFO, "ASR_GETNAMEINFO" }, + { ASR_HOSTADDR, "ASR_HOSTADDR" }, + { 0, NULL } +}; + +struct kv kv_db_type[] = { + { ASR_DB_FILE, "ASR_DB_FILE" }, + { ASR_DB_DNS, "ASR_DB_DNS" }, + { ASR_DB_YP, "ASR_DB_YP" }, + { 0, NULL } +}; + +struct kv kv_state[] = { + { ASR_STATE_INIT, "ASR_STATE_INIT" }, + { ASR_STATE_SEARCH_DOMAIN, "ASR_STATE_SEARCH_DOMAIN" }, + { ASR_STATE_LOOKUP_DOMAIN, "ASR_STATE_LOOKUP_DOMAIN" }, + { ASR_STATE_NEXT_DOMAIN, "ASR_STATE_NEXT_DOMAIN" }, + { ASR_STATE_NEXT_DB, "ASR_STATE_NEXT_DB" }, + { ASR_STATE_SAME_DB, "ASR_STATE_SAME_DB" }, + { ASR_STATE_NEXT_FAMILY, "ASR_STATE_NEXT_FAMILY" }, + { ASR_STATE_LOOKUP_FAMILY, "ASR_STATE_LOOKUP_FAMILY" }, + { ASR_STATE_NEXT_NS, "ASR_STATE_NEXT_NS" }, + { ASR_STATE_READ_RR, "ASR_STATE_READ_RR" }, + { ASR_STATE_READ_FILE, "ASR_STATE_READ_FILE" }, + { ASR_STATE_UDP_SEND, "ASR_STATE_UDP_SEND" }, + { ASR_STATE_UDP_RECV, "ASR_STATE_UDP_RECV" }, + { ASR_STATE_TCP_WRITE, "ASR_STATE_TCP_WRITE" }, + { ASR_STATE_TCP_READ, "ASR_STATE_TCP_READ" }, + { ASR_STATE_PACKET, "ASR_STATE_PACKET" }, + { ASR_STATE_SUBQUERY, "ASR_STATE_SUBQUERY" }, + { ASR_STATE_NOT_FOUND, "ASR_STATE_NOT_FOUND", }, + { ASR_STATE_HALT, "ASR_STATE_HALT" }, + { 0, NULL } +}; + +struct kv kv_transition[] = { + { ASYNC_COND, "ASYNC_COND" }, + { ASYNC_YIELD, "ASYNC_YIELD" }, + { ASYNC_DONE, "ASYNC_DONE" }, + { 0, NULL } +}; + +const char * +asr_querystr(int type) +{ + return kvlookup(kv_query_type, type); +} + +const char * +asr_transitionstr(int type) +{ + return kvlookup(kv_transition, type); +} + +void +asr_dump_async(struct async *as) +{ + asr_printf("%s fd=%i timeout=%i" + " dom_idx=%i db_idx=%i ns_idx=%i ns_cycles=%i\n", + kvlookup(kv_state, as->as_state), + as->as_fd, + as->as_timeout, + + as->as_dom_idx, + as->as_db_idx, + as->as_ns_idx, + as->as_ns_cycles); +} + +void +asr_dump_packet(FILE *f, const void *data, size_t len, int noid) +{ + char buf[1024]; + struct packed p; + struct header h; + struct query q; + struct rr rr; + int i, an, ns, ar, n; + + if (f == NULL) + return; + + packed_init(&p, (char *)data, len); + + if (unpack_header(&p, &h) == -1) { + fprintf(f, ";; BAD PACKET: %s\n", p.err); + return; + } + + fprintf(f, ";; HEADER %s\n", print_header(&h, buf, sizeof buf, noid)); + + if (h.qdcount) + fprintf(f, ";; QUERY SECTION:\n"); + for (i = 0; i < h.qdcount; i++) { + if (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 (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, + p.err); + + return; +} + +static void +asr_vdebug(const char *fmt, va_list ap) +{ + if (asr_debug) + vfprintf(stderr, fmt, ap); +} + +void +asr_printf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + asr_vdebug(fmt, ap); + va_end(ap); +} + +void +async_set_state(struct async *as, int state) +{ + asr_printf("asr: [%s@%p] %s -> %s\n", + kvlookup(kv_query_type, as->as_type), + as, + kvlookup(kv_state, as->as_state), + kvlookup(kv_state, state)); + as->as_state = state; +} diff --git a/lib/libc/asr/asr_private.h b/lib/libc/asr/asr_private.h new file mode 100644 index 00000000000..bd86912e92e --- /dev/null +++ b/lib/libc/asr/asr_private.h @@ -0,0 +1,372 @@ +/* $OpenBSD: asr_private.h,v 1.1 2012/04/14 09:24:18 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 <stdio.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 (0x7 << 4) +#define RCODE_MASK (0xf) + +#define OPCODE(v) ((v) & OPCODE_MASK) +#define RCODE(v) ((v) & RCODE_MASK) + + +struct packed { + char *data; + size_t len; + size_t offset; + const char *err; +}; + +struct header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +struct query { + char q_dname[MAXDNAME]; + uint16_t q_type; + uint16_t q_class; +}; + +struct 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_GETNETBYNAME, + ASR_GETNETBYADDR, + ASR_GETADDRINFO, + ASR_GETNAMEINFO, + ASR_HOSTADDR, +}; + +enum asr_db_type { + ASR_DB_FILE, + ASR_DB_DNS, + ASR_DB_YP, +}; + +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; + int ac_db[ASR_MAXDB]; + int ac_family[3]; + + char *ac_hostfile; + + int ac_nscount; + int ac_nstimeout; + int ac_nsretries; + struct sockaddr *ac_ns[ASR_MAXNS]; + +}; + +struct asr { + char *a_path; + time_t a_mtime; + time_t a_rtime; + struct asr_ctx *a_ctx; +}; + + +#define ASYNC_DOM_FQDN 0x00000001 +#define ASYNC_DOM_NDOTS 0x00000002 +#define ASYNC_DOM_HOSTALIAS 0x00000004 +#define ASYNC_DOM_DOMAIN 0x00000008 +#define ASYNC_DOM_ASIS 0x00000010 + +#define ASYNC_NODATA 0x00000100 +#define ASYNC_AGAIN 0x00000200 + +#define ASYNC_EXTIBUF 0x00001000 +#define ASYNC_EXTOBUF 0x00002000 + + +struct async { + int (*as_run)(struct async *, struct async_res *); + struct asr_ctx *as_ctx; + int as_type; + int as_state; + + /* cond */ + int as_timeout; + int as_fd; + + /* 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_ns_idx; + int as_ns_cycles; + + int as_count; + + union { + struct { + int flags; + uint16_t reqid; + int class; + int type; + char *dname; /* not fqdn! */ + int rcode; /* response code */ + int ancount; /* answer count */ + + /* 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 bufpos; + size_t datalen; /* for tcp io */ + } dns; + + struct { + int flags; + int class; + int type; + char *name; + struct async *subq; + int saved_h_errno; + unsigned char *ibuf; + size_t ibuflen; + size_t ibufsize; + } search; + + struct { + int flags; + int class; + int type; + char *name; + struct async *subq; + } rrset; + + struct { + char *name; + int family; + char *dname; + struct async *subq; + char addr[16]; + int addrlen; + } hostnamadr; + + struct { + char *name; + int family; + struct async *subq; + in_addr_t addr; + } netnamadr; + + 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; + struct addrinfo *aifirst; + struct addrinfo *ailast; + struct async *subq; + int flags; + } 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; + struct async *subq; + } ni; + + struct { + char *name; + int family; + int aiflags; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + + struct async *subq; + int class; + int type; + int ancount; + unsigned char *pkt; + size_t pktlen; + size_t pktpos; + FILE *file; +#define MAXTOKEN 10 + char *tokens[MAXTOKEN]; + int token_count; + int token_idx; + } host; + } 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_SEARCH_DOMAIN, + ASR_STATE_LOOKUP_DOMAIN, + ASR_STATE_NEXT_DOMAIN, + ASR_STATE_NEXT_DB, + ASR_STATE_SAME_DB, + ASR_STATE_NEXT_FAMILY, + ASR_STATE_LOOKUP_FAMILY, + ASR_STATE_NEXT_NS, + ASR_STATE_READ_RR, + ASR_STATE_READ_FILE, + 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, +}; + + +/* asr_utils.c */ +void packed_init(struct packed*, char*, size_t); +int pack_header(struct packed*, const struct header*); +int pack_query(struct packed*, uint16_t, uint16_t, const char*); +int unpack_header(struct packed*, struct header*); +int unpack_query(struct packed*, struct query*); +int unpack_rr(struct packed*, struct rr*); +int sockaddr_from_str(struct sockaddr *, int, const char *); +ssize_t dname_from_fqdn(const char*, char*, size_t); + +/* asr.c */ +struct asr_ctx *asr_use_resolver(struct asr *); +void asr_ctx_unref(struct asr_ctx *); +struct async *async_new(struct asr_ctx *, int); +void async_free(struct async *); +size_t asr_make_fqdn(const char *, const char *, char *, size_t); +size_t asr_domcat(const char *, const char *, char *, size_t); +char *asr_strdname(const char *, char *, size_t); +int asr_iter_db(struct async *); +int asr_iter_ns(struct async *); +int asr_iter_domain(struct async *, const char *, char *, size_t); +int asr_parse_namedb_line(FILE *, char **, int); + +/* <*>_async.h */ +struct async *res_query_async_ctx(const char *, int, int, unsigned char *, int, + struct asr_ctx *); +struct async *res_search_async_ctx(const char *, int, int, unsigned char *, int, + struct asr_ctx *); +struct async *gethostbyaddr_async_ctx(const void *, socklen_t, int, + struct asr_ctx *); +struct async *hostaddr_async_ctx(const char *, int, int, struct asr_ctx *); + +#ifdef DEBUG + +extern int asr_debug; + +/* asr_debug.h */ +const char *asr_querystr(int); +const char *asr_transitionstr(int); +void asr_dump(struct asr *); +void asr_dump_async(struct async *); +void asr_dump_packet(FILE *, const void *, size_t, int); +void asr_printf(const char *fmt, ...); +void async_set_state(struct async *, int); + +#else /* DEBUG */ + +#define async_set_state(a, s) do { (a)->as_state = (s); } while (0) + +#endif /* DEBUG */ diff --git a/lib/libc/asr/asr_resolver.c b/lib/libc/asr/asr_resolver.c new file mode 100644 index 00000000000..1eb36c17ab3 --- /dev/null +++ b/lib/libc/asr/asr_resolver.c @@ -0,0 +1,457 @@ +/* $OpenBSD: asr_resolver.c,v 1.1 2012/04/14 09:24:18 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 <netinet/in.h> + +#include <errno.h> +#include <resolv.h> +#include <string.h> + +#include "asr.h" + +/* + * XXX this function is actually internal to asr, but we use it here to force + * the creation a default resolver context in res_init(). + */ +struct asr_ctx *asr_use_resolver(struct asr *); +void asr_ctx_unref(struct asr_ctx *); + +static struct hostent *_gethostbyname(const char *, int); +static struct hostent *_mkstatichostent(struct hostent *); +static struct netent *_mkstaticnetent(struct netent *); + + +/* in res_init.c */ +struct __res_state _res; +struct __res_state_ext _res_ext; + +/* in res_query.c */ +int h_errno; + + +int +res_init(void) +{ + async_resolver_done(NULL); + asr_ctx_unref(asr_use_resolver(NULL)); + + return (0); +} + +int +res_send(const u_char *buf, int buflen, u_char *ans, int anslen) +{ + struct async *as; + struct async_res ar; + + if (ans == NULL || anslen <= 0) { + errno = EINVAL; + return (-1); + } + + as = res_send_async(buf, buflen, ans, anslen, NULL); + if (as == NULL) + return (-1); /* errno set */ + + async_run_sync(as, &ar); + + if (ar.ar_errno) { + errno = ar.ar_errno; + return (-1); + } + + return (ar.ar_datalen); +} + +int +res_query(const char *name, int class, int type, u_char *ans, int anslen) +{ + struct async *as; + struct async_res ar; + + if (ans == NULL || anslen <= 0) { + h_errno = NO_RECOVERY; + errno = EINVAL; + return (-1); + } + + as = res_query_async(name, class, type, ans, anslen, NULL); + if (as == NULL) { + if (errno == EINVAL) + h_errno = NO_RECOVERY; + else + h_errno = NETDB_INTERNAL; + return (-1); /* errno set */ + } + + async_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); + + return (ar.ar_datalen); +} + +int +res_search(const char *name, int class, int type, u_char *ans, int anslen) +{ + struct async *as; + struct async_res ar; + + if (ans == NULL || anslen <= 0) { + h_errno = NO_RECOVERY; + errno = EINVAL; + return (-1); + } + + as = res_search_async(name, class, type, ans, anslen, NULL); + if (as == NULL) { + if (errno == EINVAL) + h_errno = NO_RECOVERY; + else + h_errno = NETDB_INTERNAL; + return (-1); /* errno set */ + } + + async_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); + + return (ar.ar_datalen); +} + +int +getrrsetbyname(const char *name, unsigned int class, unsigned int type, + unsigned int flags, struct rrsetinfo **res) +{ + struct async *as; + struct async_res ar; + + as = getrrsetbyname_async(name, class, type, flags, NULL); + if (as == NULL) + return (errno == ENOMEM) ? ERRSET_NOMEMORY : ERRSET_FAIL; + + async_run_sync(as, &ar); + + if (ar.ar_errno) + errno = ar.ar_errno; + + *res = ar.ar_rrsetinfo; + return (ar.ar_rrset_errno); +} + +#define MAXALIASES 16 +#define MAXADDRS 16 + +/* XXX bound checks are incorrect */ +static struct hostent * +_mkstatichostent(struct hostent *h) +{ + static struct hostent r; + static char buf[4096]; + static char *aliases[MAXALIASES+1]; + static uint64_t addrbuf[64]; + static char *addr_list[MAXADDRS + 1]; + + char *pos, **c; + size_t left, n; + int naliases = 0, naddrs = 0; + + r.h_addrtype = h->h_addrtype; + r.h_length = h->h_length; + r.h_name = buf; + r.h_aliases = aliases; + r.h_addr_list = addr_list; + + pos = buf; + left = sizeof(buf); + n = strlcpy(pos, h->h_name, left); + pos += n + 1; + left -= n + 1; + + for(c = h->h_aliases; left && *c && naliases < MAXALIASES; c++) { + n = strlcpy(pos, *c, left); + if (n >= left + 1) + break; + aliases[naliases++] = pos; + pos += n + 1; + left -= n + 1; + } + aliases[naliases] = NULL; + + pos = (char*)addrbuf; + left = sizeof(addrbuf); + for(c = h->h_addr_list; *c && naddrs < MAXADDRS; c++) { + memmove(pos, *c, r.h_length); + addr_list[naddrs++] = pos; + pos += r.h_length; + left -= r.h_length; + } + addr_list[naddrs] = NULL; + + return (&r); +} + +static struct hostent * +_gethostbyname(const char *name, int af) +{ + struct async *as; + struct async_res ar; + struct hostent *h; + + if (af == -1) + as = gethostbyname_async(name, NULL); + else + as = gethostbyname2_async(name, af, NULL); + + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + async_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_hostent == NULL) + return (NULL); + + h = _mkstatichostent(ar.ar_hostent); + freehostent(ar.ar_hostent); + + return (h); +} + +struct hostent * +gethostbyname(const char *name) +{ + return _gethostbyname(name, -1); +} + +struct hostent * +gethostbyname2(const char *name, int af) +{ + return _gethostbyname(name, af); +} + +struct hostent * +gethostbyaddr(const void *addr, socklen_t len, int af) +{ + struct async *as; + struct async_res ar; + struct hostent *h; + + as = gethostbyaddr_async(addr, len, af, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + async_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_hostent == NULL) + return (NULL); + + h = _mkstatichostent(ar.ar_hostent); + freehostent(ar.ar_hostent); + + return (h); +} + +/* XXX bound checks are incorrect */ +static struct netent * +_mkstaticnetent(struct netent *n) +{ + static struct netent r; + static char buf[4096]; + static char *aliases[MAXALIASES+1]; + + char *pos, **c; + size_t left, s; + int naliases = 0; + + r.n_addrtype = n->n_addrtype; + r.n_net = n->n_net; + + r.n_name = buf; + r.n_aliases = aliases; + + pos = buf; + left = sizeof(buf); + s = strlcpy(pos, n->n_name, left); + pos += s + 1; + left -= s + 1; + + for(c = n->n_aliases; left && *c && naliases < MAXALIASES; c++) { + s = strlcpy(pos, *c, left); + if (s >= left + 1) + break; + aliases[naliases++] = pos; + pos += s + 1; + left -= s + 1; + } + aliases[naliases] = NULL; + + return (&r); +} + +struct netent * +getnetbyname(const char *name) +{ + struct async *as; + struct async_res ar; + struct netent *n; + + as = getnetbyname_async(name, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + async_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_netent == NULL) + return (NULL); + + n = _mkstaticnetent(ar.ar_netent); + freenetent(ar.ar_netent); + + return (n); +} + +struct netent * +getnetbyaddr(in_addr_t net, int type) +{ + struct async *as; + struct async_res ar; + struct netent *n; + + as = getnetbyaddr_async(net, type, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + async_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_netent == NULL) + return (NULL); + + n = _mkstaticnetent(ar.ar_netent); + freenetent(ar.ar_netent); + + return (n); +} + +int +getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + struct async *as; + struct async_res ar; + + as = getaddrinfo_async(hostname, servname, hints, NULL); + if (as == NULL) + return ((errno == ENOMEM) ? EAI_MEMORY : EAI_SYSTEM); + + async_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + *res = ar.ar_addrinfo; + + return (ar.ar_gai_errno); +} + +int +getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + struct async *as; + struct async_res ar; + + as = getnameinfo_async(sa, salen, host, hostlen, serv, servlen, flags, + NULL); + if (as == NULL) + return ((errno == ENOMEM) ? EAI_MEMORY : EAI_SYSTEM); + + async_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + + return (ar.ar_gai_errno); +} + +/* XXX see what to do */ +void +sethostent(int stayopen) +{ +} + +void +endhostent(void) +{ +} + +/* from 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); +} diff --git a/lib/libc/asr/asr_utils.c b/lib/libc/asr/asr_utils.c new file mode 100644 index 00000000000..2329de71850 --- /dev/null +++ b/lib/libc/asr/asr_utils.c @@ -0,0 +1,450 @@ +/* $OpenBSD: asr_utils.c,v 1.1 2012/04/14 09:24:18 eric 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 <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "asr.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 packed*, void*, size_t); +static int unpack_u16(struct packed*, uint16_t*); +static int unpack_u32(struct packed*, uint32_t*); +static int unpack_inaddr(struct packed*, struct in_addr*); +static int unpack_in6addr(struct packed*, struct in6_addr*); +static int unpack_dname(struct packed*, char*, size_t); + +static int pack_data(struct packed*, const void*, size_t); +static int pack_u16(struct packed*, uint16_t); +static int pack_dname(struct packed*, const char*); + +static int +dname_check_label(const char *s, size_t l) +{ + if (l == 0 || l > 63) + return (-1); + + for(l--; l; l--, s++) + if (!(isalnum(*s) || *s == '_' || *s == '-')) + return (-1); + + return (0); +} + +ssize_t +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 = 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 +packed_init(struct packed *pack, char *data, size_t len) +{ + pack->data = data; + pack->len = len; + pack->offset = 0; + pack->err = NULL; +} + +static int +unpack_data(struct packed *p, void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len - p->offset < len) { + p->err = "too short"; + return (-1); + } + + memmove(data, p->data + p->offset, len); + p->offset += len; + + return (0); +} + +static int +unpack_u16(struct packed *p, uint16_t *u16) +{ + if (unpack_data(p, u16, 2) == -1) + return (-1); + + *u16 = ntohs(*u16); + + return (0); +} + +static int +unpack_u32(struct packed *p, uint32_t *u32) +{ + if (unpack_data(p, u32, 4) == -1) + return (-1); + + *u32 = ntohl(*u32); + + return (0); +} + +static int +unpack_inaddr(struct packed *p, struct in_addr *a) +{ + return (unpack_data(p, a, 4)); +} + +static int +unpack_in6addr(struct packed *p, struct in6_addr *a6) +{ + return (unpack_data(p, a6, 16)); +} + +static int +unpack_dname(struct packed *p, char *dst, size_t max) +{ + ssize_t e; + + if (p->err) + return (-1); + + e = dname_expand(p->data, p->len, p->offset, &p->offset, dst, max); + if (e == -1) { + p->err = "bad domain name"; + return (-1); + } + if (e < 0 || e > MAXDNAME) { + p->err = "domain name too long"; + return (-1); + } + + return (0); +} + +int +unpack_header(struct packed *p, struct 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 +unpack_query(struct packed *p, struct 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 +unpack_rr(struct packed *p, struct 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 = "too short"; + 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->data + 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 = "bad dlen"; + + return (p->err) ? (-1) : (0); +} + +static int +pack_data(struct packed *p, const void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len < p->offset + len) { + p->err = "no space"; + return (-1); + } + + memmove(p->data + p->offset, data, len); + p->offset += len; + + return (0); +} + +static int +pack_u16(struct packed *p, uint16_t v) +{ + v = htons(v); + + return (pack_data(p, &v, 2)); +} + +static int +pack_dname(struct packed *p, const char *dname) +{ + /* dname compression would be nice to have here. + * need additionnal context. + */ + return (pack_data(p, dname, strlen(dname) + 1)); +} + +int +pack_header(struct packed *p, const struct header *h) +{ + struct 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 +pack_query(struct packed *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 +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; + + switch (family) { + case PF_UNSPEC: + if (sockaddr_from_str(sa, PF_INET, str) == 0) + return (0); + return 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); + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_family = PF_INET; + sin->sin_addr.s_addr = ina.s_addr; + return (0); + + case PF_INET6: + if (inet_pton(PF_INET6, str, &in6a) != 1) + return (-1); + + sin6 = (struct sockaddr_in6 *)sa; + memset(sin6, 0, sizeof *sin6); + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = PF_INET6; + sin6->sin6_addr = in6a; + return (0); + + default: + break; + } + + return (-1); +} diff --git a/lib/libc/asr/async_resolver.3 b/lib/libc/asr/async_resolver.3 new file mode 100644 index 00000000000..23e46f42656 --- /dev/null +++ b/lib/libc/asr/async_resolver.3 @@ -0,0 +1,358 @@ +.\" $OpenBSD: async_resolver.3,v 1.1 2012/04/14 09:24:18 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. +.\" +.Dd $Mdocdate: April 14 2012 $ +.Dt ASYN_RESOLVER 3 +.Os +.Sh NAME +.Nm async_resolver , +.Nm async_resolver_done , +.Nm async_run , +.Nm async_run_sync , +.Nm async_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 freehostent , +.Nm getnetbyname_async , +.Nm getnetbyaddr_async , +.Nm freenetent , +.Nm getaddrinfo_async , +.Nm getnameinfo_async +.Nd asynchronous resolver functions +.Sh SYNOPSIS +.Fd #include <asr.h> +.Ft struct asr* +.Fn async_resolver "const char *conf" +.Ft void +.Fn async_resolver_done "struct asr *asr" +.Ft int +.Fn async_run "struct async *as" "struct async_res *ar" +.Ft int +.Fn async_run_sync "struct async *as" "struct async_res *ar" +.Ft void +.Fn async_abort "struct async *as" +.Ft struct async* +.Fn res_send_async "const unsigned char *pkt" "int pktlen" "unsigned char *ans" "int anslen" "struct asr *asr" +.Ft struct async* +.Fn res_query_async "const char *name" "int class" "int type" "unsigned char *ans" "int anslen" "struct asr *asr" +.Ft struct async* +.Fn getrrsetbyname_async "const char *hostname" "unsigned int rdclass" "unsigned int rdtype" "unsigned int flags" "struct asr *asr" +.Ft struct async* +.Fn gethostbyname_async "const char *name" "struct asr *asr" +.Ft struct async* +.Fn gethostbyname2_async "const char *name" "int af" "struct asr *asr" +.Ft struct async* +.Fn gethostbyaddr_async "const void *addr" "socklen_t len" "int af" "struct asr *asr" +.Ft void +.Fn freehostent "struct hostent *h" +.Fn getnetbyname_async "const char *name" "struct asr *asr" +.Ft struct async* +.Fn getnetbyaddr_async "in_addr_t net" "int type" "struct asr *asr" +.Ft void +.Fn freenetent "struct netent *n" +.Ft struct async* +.Fn getaddrinfo_async "const char *hostname" "const char *servname" "const struct addrinfo *hints" "struct asr *asr" +.Ft struct async* +.Fn getnameinfo_async "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags" "struct asr *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. +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 (fetch the result, +or wait until the fd conditions are met), and call back into the resolving +machinery when it is ready to proceed. +.Pp +.Fn async_resolver +is the function used to create a new resolver context. +The +.Fa conf +argument is a path to the resolver configuration file +as described in +.Xr resolv.conf 5 . +If NULL, the default +.Pa /etc/resolv.conf +file is used. +The context tracks file changes to automatically update its configuration +if needed, replacing the current setup if a valid one can be reloaded from +the file. +If configuration file cannot be loaded at context creation time, it falls +back to the equivalent of: +.Bd -literal -offset indent +lookup bind file +nameserver 127.0.0.1 +.Ed +.Pp +If the first character of the +.Fa conf +string is a '!', the configuration is read from the rest of the string rather +than loaded from a file. +No further update occurs in this case. +.Pp +.Fn async_resolver_done +is used to discard the +.Fa asr +context when it is not used anymore. +Once called, that context is invalidated and cannot be used to create new +queries. +Internally, the context is refcounted, so that existing queries made against +it will be able to complete safely. +All relevant resources are effectively +freed when all such queries are cleared. +.Pp +The +.Fn async_run +function drives the resolving process. +It runs the +.Fa as +asynchronous query until an answer 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 -offset indent +struct async_res { + int ar_cond; + int ar_fd; + int ar_timeout; + + int ar_errno; + int ar_h_errno; + int ar_gai_errno; + int ar_rrset_errno; + + int ar_rcode; + void *ar_data; + int ar_datalen; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } ar_sa; + + char *ar_cname; + int ar_count; + + 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 "ASYNC_YIELD " -offset indent +.It ASYNC_COND +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 ASYNC_READ or ASYNC_WRITE. +.It Fa ar_fd +The file descriptor waiting for an IO operation. +.It Fa ar_timeout +The timeout, expressed in milliseconds. +.El +.Pp +The caller is expected to call +.Fn async_run +again once the condition holds or the timeout expires. +.It ASYNC_DONE +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 address is invalidated. +.It ASR_YIELD +A partial result is available. +This code is used for async queries that behave as iterators over the result +set. +The query-specific members of +.Fa ar +are set accordingly and the resolving process can be resumed by calling +.Fn async_run . +.El +.Pp +The +.Fn async_run_sync +function is a wrapper around +.Fn async_run +that handles the read/write conditions, thus falling back to a blocking +interface. +It only returns partial and complete results through ASYNC_YIELD and ASYNC_DONE +respectively. +.Pp +The +.Fn async_abort +function clears a running query. +It can be called after a partial result has been retrieved or when the query +is waiting on a file descriptor. +Note that a completed query is already cleared when +.Fn async_run +returns, so +.Fn async_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 NULL if they could not allocate the necessary resources +to initiate the query. +All other errors (especially invalid parameters) +are reported when calling +.Fn async_run . +They usually have the same interface as an exisiting resolver function, with +an additionnal +.Ar asr +contex argument, which specifies the context to use for this request. +If NULL, the default thread-local context is used. +.Pp +The +.Fn res_send_async , 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 they take a resolver context as +last argument, and the return value is found upon completion in the +.Fa ar_datalen +member of the response structure. +In addition, the +.Fa ar_sa +union contains the address of the DNS server that sent the response, +.Fa ar_rcode +contains the code returned by the server in the DNS responce packet, and +.Fa ar_count +contains the number of answer in the packet. +If no answer buffer is provided, a new one is allocated to fit the response +and returned as the +.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 +.Fn 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. +Therefore, the pointer must be freed through the new +.Fn freehostent +call. +.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 +.Fn freenetent . +.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 . +The +.Fa ar_count +contains the number of elements in the list. +.Pp +.Sh SEE ALSO +.Xr res_send 3 , +.Xr getrrsetbyname 3 , +.Xr gethostbyname 3 , +.Xr getnetbyname 3 , +.Xr getaddrinfo 3 , +.Xr getnameinfo 3 , +.Xr resolv.conf 5 +.Sh LIMITATIONS +This DNS resolver implementation doesn't support +the EDNS0 protocol extension yet. +.Pp +The current implementation does not handle YP databases. diff --git a/lib/libc/asr/getaddrinfo_async.c b/lib/libc/asr/getaddrinfo_async.c new file mode 100644 index 00000000000..b6c079c7850 --- /dev/null +++ b/lib/libc/asr/getaddrinfo_async.c @@ -0,0 +1,500 @@ +/* $OpenBSD: getaddrinfo_async.c,v 1.1 2012/04/14 09:24:18 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/uio.h> + +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "asr_private.h" + +struct match { + int family; + int socktype; + int protocol; +}; + +static int getaddrinfo_async_run(struct async *, struct async_res *); +static int get_port(const char *, const char *, int); +static int iter_family(struct async *, int); +static int add_sockaddr(struct async *, struct sockaddr *, const char *); + +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) +/* Do not match SOCK_RAW unless explicitely specified */ +#define MATCH_SOCKTYPE(a, b) ((a) == matches[(b)].socktype || ((a) == 0 && \ + matches[(b)].socktype != SOCK_RAW)) + +struct async * +getaddrinfo_async(const char *hostname, const char *servname, + const struct addrinfo *hints, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; + + ac = asr_use_resolver(asr); + if ((as = async_new(ac, ASR_GETADDRINFO)) == NULL) + goto abort; /* errno set */ + as->as_run = getaddrinfo_async_run; + + if (hostname && (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; + } + + asr_ctx_unref(ac); + return (as); + abort: + if (as) + async_free(as); + asr_ctx_unref(ac); + return (NULL); +} + +static int +getaddrinfo_async_run(struct async *as, struct async_res *ar) +{ + const char *str; + struct addrinfo *ai; + int i, family, r; + char fqdn[MAXDNAME]; + 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; + async_set_state(as, ASR_STATE_HALT); + ar->ar_errno = 0; + ar->ar_h_errno = NETDB_SUCCESS; + ar->ar_gai_errno = 0; + + if (as->as.ai.hostname == NULL && + as->as.ai.servname == NULL) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_NONAME; + break; + } + + ai = &as->as.ai.hints; + + if (ai->ai_addrlen || + ai->ai_canonname || + ai->ai_addr || + ai->ai_next) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_BADHINTS; + break; + } + + if (ai->ai_flags & ~AI_MASK || + (ai->ai_flags & AI_CANONNAME && ai->ai_flags & AI_FQDN)) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_BADFLAGS; + break; + } + + if (ai->ai_family != PF_UNSPEC && + ai->ai_family != PF_INET && + ai->ai_family != PF_INET6) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_FAMILY; + break; + } + + if (ai->ai_socktype && + ai->ai_socktype != SOCK_DGRAM && + ai->ai_socktype != SOCK_STREAM && + ai->ai_socktype != SOCK_RAW) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_SOCKTYPE; + break; + } + + if (ai->ai_protocol && + ai->ai_protocol != IPPROTO_UDP && + ai->ai_protocol != IPPROTO_TCP) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_PROTOCOL; + break; + } + + if (ai->ai_socktype == SOCK_RAW && + as->as.ai.servname != NULL) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_SERVICE; + 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) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_BADHINTS; + break; + } + + if (as->as.ai.servname) { + as->as.ai.port_udp = get_port(as->as.ai.servname, + "udp", as->as.ai.hints.ai_flags & AI_NUMERICSERV); + 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 < 0 || as->as.ai.port_udp < 0) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_SERVICE; + break; + } + } + + /* 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 */ + sockaddr_from_str(&sa.sa, family, str); + if ((r = add_sockaddr(as, &sa.sa, NULL))) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = r; + async_set_state(as, ASR_STATE_HALT); + break; + } + } + if (ar->ar_gai_errno == 0 && as->as_count == 0) { + ar->ar_h_errno = NO_DATA; + ar->ar_gai_errno = EAI_NODATA; + } + break; + } + + /* Try numeric addresses first */ + for(family = iter_family(as, 1); + family != -1; + family = iter_family(as, 0)) { + + if (sockaddr_from_str(&sa.sa, family, + as->as.ai.hostname) == -1) + continue; + + if ((r = add_sockaddr(as, &sa.sa, NULL))) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = r; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_HALT); + break; + } + if (ar->ar_gai_errno || as->as_count) + break; + + if (ai->ai_flags & AI_NUMERICHOST) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Starting domain lookup */ + async_set_state(as, ASR_STATE_SEARCH_DOMAIN); + break; + + case ASR_STATE_SEARCH_DOMAIN: + + r = asr_iter_domain(as, as->as.ai.hostname, fqdn, sizeof(fqdn)); + if (r == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + if (r > sizeof(fqdn)) { + ar->ar_errno = EINVAL; + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_OVERFLOW; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* + * Create a subquery to lookup the host addresses. + * We use the special hostaddr_async() API, which has the + * nice property of honoring the "lookup" and "family" keyword + * in the configuration, thus returning the right address + * families in the right order, and thus fixing the current + * getaddrinfo() feature documented in the BUGS section of + * resolver.conf(5). + */ + as->as.ai.subq = hostaddr_async_ctx(fqdn, + as->as.ai.hints.ai_family, as->as.ai.hints.ai_flags, + as->as_ctx); + if (as->as.ai.subq == NULL) { + ar->ar_errno = errno; + if (errno == EINVAL) { + ar->ar_h_errno = NO_RECOVERY; + ar->ar_gai_errno = EAI_FAIL; + } else { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = EAI_MEMORY; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + async_set_state(as, ASR_STATE_LOOKUP_DOMAIN); + break; + + case ASR_STATE_LOOKUP_DOMAIN: + + /* Run the subquery */ + if ((r = async_run(as->as.ai.subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + /* Got one more address, use it to extend the result list. */ + if (r == ASYNC_YIELD) { + if ((r = add_sockaddr(as, &ar->ar_sa.sa, + ar->ar_cname))) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = r; + async_set_state(as, ASR_STATE_HALT); + } + if (ar->ar_cname) + free(ar->ar_cname); + break; + } + + /* + * The subquery is done. Stop there if we have at least one + * answer. + */ + as->as.ai.subq = NULL; + if (ar->ar_count) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* + * No anwser for this domain, but we might be suggested to + * try again later, so remember this. Then search the next + * domain. + */ + if (ar->ar_gai_errno == EAI_AGAIN) + as->as.ai.flags |= ASYNC_AGAIN; + async_set_state(as, ASR_STATE_SEARCH_DOMAIN); + break; + + case ASR_STATE_NOT_FOUND: + + /* + * No result found. Maybe we can try again. + */ + ar->ar_errno = 0; + if (as->as.ai.flags & ASYNC_AGAIN) { + ar->ar_h_errno = TRY_AGAIN; + ar->ar_gai_errno = EAI_AGAIN; + } else { + ar->ar_h_errno = NO_DATA; + ar->ar_gai_errno = EAI_NODATA; + } + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + /* Set the results. */ + + 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_h_errno = NETDB_INTERNAL; + 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) +{ + struct servent se; + struct servent_data sed; + int port, r; + 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); + + memset(&sed, 0, sizeof(sed)); + r = getservbyname_r(servname, proto, &se, &sed); + port = ntohs(se.s_port); + endservent_r(&sed); + + if (r == -1) + return (-1); /* not found */ + + 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 async *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 +add_sockaddr(struct async *as, struct sockaddr *sa, const char *cname) +{ + struct addrinfo *ai; + int i, port; + + 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; + + if (matches[i].protocol == IPPROTO_TCP) + port = as->as.ai.port_tcp; + else if (matches[i].protocol == IPPROTO_UDP) + port = as->as.ai.port_udp; + else + port = 0; + + ai = calloc(1, sizeof(*ai) + sa->sa_len); + if (ai == NULL) + return (EAI_MEMORY); + ai->ai_family = sa->sa_family; + ai->ai_socktype = matches[i].socktype; + ai->ai_protocol = matches[i].protocol; + ai->ai_addrlen = sa->sa_len; + 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->sa_len); + 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); +} diff --git a/lib/libc/asr/gethostnamadr_async.c b/lib/libc/asr/gethostnamadr_async.c new file mode 100644 index 00000000000..f4a777d39ac --- /dev/null +++ b/lib/libc/asr/gethostnamadr_async.c @@ -0,0 +1,592 @@ +/* $OpenBSD: gethostnamadr_async.c,v 1.1 2012/04/14 09:24:18 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 <arpa/inet.h> +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "asr_private.h" + + +#define MAXALIASES 16 +#define MAXADDRS 16 + +ssize_t addr_as_fqdn(const char *, int, char *, size_t); + +static int gethostnamadr_async_run(struct async *, struct async_res *); +static struct hostent *hostent_alloc(int); +static int hostent_set_cname(struct hostent *, const char *, int); +static int hostent_add_alias(struct hostent *, const char *, int); +static int hostent_add_addr(struct hostent *, const void *, int); +static int hostent_file_match(FILE *, int, int, int, const char *, + char *, char **, int); +static int hostent_from_packet(struct hostent *, int, char *, size_t); + +struct async * +gethostbyname_async(const char *name, struct asr *asr) +{ + return gethostbyname2_async(name, AF_INET, asr); +} + +struct async * +gethostbyname2_async(const char *name, int af, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; + + /* the original segfaults */ + if (name == NULL) { + errno = EINVAL; + return (NULL); + } + + ac = asr_use_resolver(asr); + if ((as = 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) + async_free(as); + asr_ctx_unref(ac); + return (NULL); +} + +struct async * +gethostbyaddr_async(const void *addr, socklen_t len, int af, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; + + ac = asr_use_resolver(asr); + as = gethostbyaddr_async_ctx(addr, len, af, ac); + asr_ctx_unref(ac); + + return (as); +} + +struct async * +gethostbyaddr_async_ctx(const void *addr, socklen_t len, int af, + struct asr_ctx *ac) +{ + struct async *as; + + if ((as = 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) + async_free(as); + return (NULL); +} + +static int +gethostnamadr_async_run(struct async *as, struct async_res *ar) +{ + struct hostent *e; + int i, n, r, type; + FILE *f; + char *toks[MAXTOKEN], addr[16], dname[MAXDNAME], *data; + + 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) + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + else + async_set_state(as, ASR_STATE_NEXT_DB); + break; + + case ASR_STATE_NEXT_DOMAIN: + + r = asr_iter_domain(as, as->as.hostnamadr.name, dname, sizeof(dname)); + if (r == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + + if (as->as.hostnamadr.dname) + free(as->as.hostnamadr.dname); + if ((as->as.hostnamadr.dname = strdup(dname)) == NULL) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = errno; + async_set_state(as, ASR_STATE_HALT); + } + + as->as_db_idx = 0; + async_set_state(as, ASR_STATE_NEXT_DB); + break; + + case ASR_STATE_NEXT_DB: + + if (asr_iter_db(as) == -1) { + if (as->as_type == ASR_GETHOSTBYNAME) + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + else + 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.hostnamadr.subq = res_query_async_ctx( + as->as.hostnamadr.dname, + C_IN, type, NULL, 0, as->as_ctx); + } else { + addr_as_fqdn(as->as.hostnamadr.addr, + as->as.hostnamadr.family, + dname, sizeof(dname)); + as->as.hostnamadr.subq = res_query_async_ctx( + dname, C_IN, T_PTR, NULL, 0, as->as_ctx); + } + + if (as->as.hostnamadr.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(as->as_ctx->ac_hostfile, "r")) == NULL) + break; + + if (as->as_type == ASR_GETHOSTBYNAME) + data = as->as.hostnamadr.dname; + else + data = as->as.hostnamadr.addr; + + if (( n = hostent_file_match(f, as->as_type, + as->as.hostnamadr.family, + as->as.hostnamadr.addrlen, data, addr, + toks, MAXTOKEN)) == -1) { + fclose(f); + break; + } + e = hostent_alloc(as->as.hostnamadr.family); + if (e == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + hostent_set_cname(e, toks[1], 0); + for (i = 2; i < n; i ++) + hostent_add_alias(e, toks[i], 0); + hostent_add_addr(e, addr, e->h_length); + fclose(f); + + ar->ar_h_errno = NETDB_SUCCESS; + ar->ar_hostent = e; + async_set_state(as, ASR_STATE_HALT); + break; + } + break; + + case ASR_STATE_SUBQUERY: + + /* Run the DNS subquery. */ + + if ((r = async_run(as->as.hostnamadr.subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + /* Done. */ + as->as.hostnamadr.subq = NULL; + + if (ar->ar_datalen == -1) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + /* If we got a packet but no anwser, use the next DB. */ + if (ar->ar_count == 0) { + free(ar->ar_data); + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + /* Read the hostent from the packet. */ + if ((e = hostent_alloc(as->as.hostnamadr.family)) == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + free(ar->ar_data); + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_type == ASR_GETHOSTBYADDR) { + e->h_addr_list[0] = malloc(as->as.hostnamadr.addrlen); + if (e->h_addr_list[0]) + memmove(e->h_addr_list[0], + as->as.hostnamadr.addr, + as->as.hostnamadr.addrlen); + } + + hostent_from_packet(e, as->as_type, ar->ar_data, + ar->ar_datalen); + free(ar->ar_data); + + /* + * No address found in the dns packet. The blocking version + * reports this as an error. + */ + if (as->as_type == ASR_GETHOSTBYNAME && + e->h_addr_list[0] == NULL) { + freehostent(e); + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + ar->ar_h_errno = NETDB_SUCCESS; + ar->ar_hostent = e; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_NOT_FOUND: + ar->ar_errno = 0; + ar->ar_h_errno = HOST_NOT_FOUND; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_h_errno) + ar->ar_hostent = 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; +} + +/* + * Lookup the first matching entry in the hostfile, either by address or by + * name. Split the matching line into tokens in the "token" array and return + * the number of tokens. + */ +static int +hostent_file_match(FILE *f, int type, int family, int len, const char *data, + char *addr, char **tokens, int ntokens) +{ + int n, i; + + for(;;) { + n = asr_parse_namedb_line(f, tokens, MAXTOKEN); + if (n == -1) + return (-1); + + if (type == ASR_GETHOSTBYNAME) { + for (i = 1; i < n; i++) { + if (strcasecmp(data, tokens[i])) + continue; + if (inet_pton(family, tokens[0], addr) == 1) + return (n); + } + continue; + } + + if (inet_pton(family, tokens[0], addr) == 1) + if (memcmp(addr, data, len) == 0) + return (n); + } +} + +/* + * Fill the hostent from the given DNS packet. + */ +static int +hostent_from_packet(struct hostent *h, int action, char *pkt, size_t pktlen) +{ + struct packed p; + struct header hdr; + struct query q; + struct rr rr; + int r; + + packed_init(&p, pkt, pktlen); + unpack_header(&p, &hdr); + for(; hdr.qdcount; hdr.qdcount--) + unpack_query(&p, &q); + for(; hdr.ancount; hdr.ancount--) { + unpack_rr(&p, &rr); + if (rr.rr_class != C_IN) + continue; + switch (rr.rr_type) { + + case T_CNAME: + if (action == ASR_GETHOSTBYNAME) + r = hostent_add_alias(h, rr.rr_dname, 1); + else + r = hostent_set_cname(h, rr.rr_dname, 1); + break; + + case T_PTR: + if (action != ASR_GETHOSTBYADDR) + continue; + r = hostent_set_cname(h, rr.rr.ptr.ptrname, 1); + /* XXX See if we need MULTI_PTRS_ARE_ALIASES */ + break; + + case T_A: + if (h->h_addrtype != AF_INET) + break; + r = hostent_set_cname(h, rr.rr_dname, 1); + r = hostent_add_addr(h, &rr.rr.in_a.addr, 4); + break; + + case T_AAAA: + if (h->h_addrtype != AF_INET6) + break; + r = hostent_set_cname(h, rr.rr_dname, 1); + r = hostent_add_addr(h, &rr.rr.in_aaaa.addr6, 16); + break; + } + } + + return (0); +} + +static struct hostent * +hostent_alloc(int family) +{ + struct hostent *h; + + h = calloc(1, sizeof *h); + if (h == NULL) + return (NULL); + + h->h_aliases = calloc(MAXALIASES, sizeof *h->h_aliases); + h->h_addr_list = calloc(MAXADDRS, sizeof *h->h_addr_list); + if (h->h_aliases == NULL || h->h_addr_list == NULL) { + freehostent(h); + return (NULL); + } + h->h_addrtype = family; + h->h_length = (family == AF_INET) ? 4 : 16; + + return (h); +} + +static int +hostent_set_cname(struct hostent *h, const char *name, int isdname) +{ + char buf[MAXDNAME]; + + if (h->h_name) + return (0); + + if (isdname) { + asr_strdname(name, buf, sizeof buf); + buf[strlen(buf) - 1] = '\0'; + h->h_name = strdup(buf); + } else { + h->h_name = strdup(name); + } + if (h->h_name == NULL) + return (-1); + + return (0); +} + +static int +hostent_add_alias(struct hostent *h, const char *name, int isdname) +{ + char buf[MAXDNAME]; + size_t i; + + for (i = 0; i < MAXALIASES; i++) + if (h->h_aliases[i] == NULL) + break; + if (i == MAXALIASES) + return (0); + + if (isdname) { + asr_strdname(name, buf, sizeof buf); + buf[strlen(buf)-1] = '\0'; + h->h_aliases[i] = strdup(buf); + } else { + h->h_aliases[i] = strdup(name); + } + if (h->h_aliases[i] == NULL) + return (-1); + + return (0); +} + +static int +hostent_add_addr(struct hostent *h, const void *addr, int size) +{ + int i; + + for (i = 0; i < MAXADDRS; i++) + if (h->h_addr_list[i] == NULL) + break; + if (i == MAXADDRS) + return (0); + + h->h_addr_list[i] = malloc(size); + if (h->h_addr_list[i] == NULL) + return (-1); + memmove(h->h_addr_list[i], addr, size); + + return (0); +} + +void +freehostent(struct hostent *h) +{ + char **c; + + free(h->h_name); + for (c = h->h_aliases; *c; c++) + free(*c); + free(h->h_aliases); + for (c = h->h_addr_list; *c; c++) + free(*c); + free(h->h_addr_list); + free(h); +} + + +ssize_t +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(*((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 = (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/lib/libc/asr/getnameinfo_async.c b/lib/libc/asr/getnameinfo_async.c new file mode 100644 index 00000000000..1ba853d0a7d --- /dev/null +++ b/lib/libc/asr/getnameinfo_async.c @@ -0,0 +1,261 @@ +/* $OpenBSD: getnameinfo_async.c,v 1.1 2012/04/14 09:24:18 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 <arpa/inet.h> +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "asr_private.h" + +static int getnameinfo_async_run(struct async *, struct async_res *); +static int _servname(struct async *); +static int _numerichost(struct async *); + +struct async * +getnameinfo_async(const struct sockaddr *sa, socklen_t slen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; + + ac = asr_use_resolver(asr); + if ((as = 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)); + + as->as.ni.sa.sa.sa_len = slen; + as->as.ni.hostname = host; + as->as.ni.hostnamelen = hostlen; + as->as.ni.servname = serv; + as->as.ni.servnamelen = servlen; + as->as.ni.flags = flags; + + asr_ctx_unref(ac); + return (as); + + abort: + if (as) + async_free(as); + asr_ctx_unref(ac); + return (NULL); +} + +static int +getnameinfo_async_run(struct async *as, struct async_res *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 && + (as->as.ni.sa.sa.sa_len != sizeof (as->as.ni.sa.sain))) || + (as->as.ni.sa.sa.sa_family == AF_INET6 && + (as->as.ni.sa.sa.sa_len != sizeof (as->as.ni.sa.sain6)))) { + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* 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) { + ar->ar_errno = errno; + if (ar->ar_errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else if (ar->ar_errno == ENOSPC) + ar->ar_gai_errno = EAI_OVERFLOW; + else + 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.ni.subq = gethostbyaddr_async_ctx(addr, addrlen, + as->as.ni.sa.sa.sa_family, + as->as_ctx); + if (as->as.ni.subq == NULL) { + ar->ar_errno = errno; + 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 = async_run(as->as.ni.subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + /* + * Request done. + */ + as->as.ni.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) { + ar->ar_errno = errno; + if (ar->ar_errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else if (ar->ar_errno == ENOSPC) + ar->ar_gai_errno = EAI_OVERFLOW; + else + 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; + freehostent(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_h_errno = NETDB_INTERNAL; + 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 async *as) +{ + struct servent s; + struct servent_data sd; + 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)) { + memset(&sd, 0, sizeof (sd)); + if (getservbyport_r(port, + (as->as.ni.flags & NI_DGRAM) ? "udp" : "tcp", + &s, &sd) != -1) { + r = strlcpy(buf, s.s_name, buflen) >= buflen; + endservent_r(&sd); + return (r ? -1 : 0); + } + } + + r = snprintf(buf, buflen, "%u", ntohs(port)); + if (r == -1 || r >= buflen) + return (-1); + + return (0); +} + +/* + * Write the numeric address + */ +static int +_numerichost(struct async *as) +{ + 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) + /* errno set */ + return (-1); + + return (0); +} diff --git a/lib/libc/asr/getnetnamadr_async.c b/lib/libc/asr/getnetnamadr_async.c new file mode 100644 index 00000000000..0bc8a36cfb0 --- /dev/null +++ b/lib/libc/asr/getnetnamadr_async.c @@ -0,0 +1,418 @@ +/* $OpenBSD: getnetnamadr_async.c,v 1.1 2012/04/14 09:24:18 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 <arpa/inet.h> +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "asr_private.h" + +#define MAXALIASES 16 + +ssize_t addr_as_fqdn(const char *, int, char *, size_t); + +static int getnetnamadr_async_run(struct async *, struct async_res *); +static struct netent *netent_alloc(int); +static int netent_set_cname(struct netent *, const char *, int); +static int netent_add_alias(struct netent *, const char *, int); +static int netent_file_match(FILE *, int, const char *, char **, int); +static int netent_from_packet(struct netent *, int, char *, size_t); + +struct async * +getnetbyname_async(const char *name, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; + + /* The current resolver segfaults. */ + if (name == NULL) { + errno = EINVAL; + return (NULL); + } + + ac = asr_use_resolver(asr); + if ((as = async_new(ac, ASR_GETNETBYNAME)) == NULL) + goto abort; /* errno set */ + as->as_run = getnetnamadr_async_run; + + as->as.netnamadr.family = AF_INET; + as->as.netnamadr.name = strdup(name); + if (as->as.netnamadr.name == NULL) + goto abort; /* errno set */ + + asr_ctx_unref(ac); + return (as); + + abort: + if (as) + async_free(as); + asr_ctx_unref(ac); + return (NULL); +} + +struct async * +getnetbyaddr_async(in_addr_t net, int family, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; + + ac = asr_use_resolver(asr); + if ((as = async_new(ac, ASR_GETNETBYADDR)) == NULL) + goto abort; /* errno set */ + as->as_run = getnetnamadr_async_run; + + as->as.netnamadr.family = family; + as->as.netnamadr.addr = net; + + asr_ctx_unref(ac); + return (as); + + abort: + if (as) + async_free(as); + asr_ctx_unref(ac); + return (NULL); +} + +static int +getnetnamadr_async_run(struct async *as, struct async_res *ar) +{ + struct netent *e; + int i, n, r, type; + FILE *f; + char *toks[MAXTOKEN], dname[MAXDNAME], *name, *data; + in_addr_t in; + + next: + switch(as->as_state) { + + case ASR_STATE_INIT: + + if (as->as.netnamadr.family != AF_INET) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = EAFNOSUPPORT; + 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: + + if (as->as_type == ASR_GETNETBYNAME) { + type = T_A; + /* + * I think we want to do the former, but our + * resolver is doing the following, so let's + * preserve bugward-compatibility there. + */ + type = T_PTR; + name = as->as.netnamadr.name; + as->as.netnamadr.subq = res_search_async_ctx( + name, C_IN, type, NULL, 0, as->as_ctx); + } else { + type = T_PTR; + name = dname; + + in = htonl(as->as.netnamadr.addr); + addr_as_fqdn((char*)&in, + as->as.netnamadr.family, + dname, sizeof(dname)); + as->as.netnamadr.subq = res_query_async_ctx( + name, C_IN, type, NULL, 0, as->as_ctx); + } + + if (as->as.netnamadr.subq == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + } + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_DB_FILE: + + if ((f = fopen("/etc/networks", "r")) == NULL) + break; + + if (as->as_type == ASR_GETNETBYNAME) + data = as->as.netnamadr.name; + else + data = (void*)&as->as.netnamadr.addr; + n = netent_file_match(f, as->as_type, data, toks, + MAXTOKEN); + if (n == -1) { + fclose(f); + break; + } + e = netent_alloc(as->as.netnamadr.family); + if (e == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + netent_set_cname(e, toks[0], 0); + for (i = 2; i < n; i ++) + netent_add_alias(e, toks[i], 0); + e->n_net = inet_network(toks[1]); + fclose(f); + + ar->ar_h_errno = NETDB_SUCCESS; + ar->ar_netent = e; + async_set_state(as, ASR_STATE_HALT); + break; + } + break; + + case ASR_STATE_SUBQUERY: + + if ((r = async_run(as->as.netnamadr.subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + as->as.netnamadr.subq = NULL; + + if (ar->ar_datalen == -1) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + /* Got packet, but no answer */ + if (ar->ar_count == 0) { + free(ar->ar_data); + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + if ((e = netent_alloc(as->as.netnamadr.family)) == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + free(ar->ar_data); + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_type == ASR_GETNETBYADDR) + e->n_net = as->as.netnamadr.addr; + + netent_from_packet(e, as->as_type, ar->ar_data, ar->ar_datalen); + free(ar->ar_data); + + /* + * No address found in the dns packet. The blocking version + * reports this as an error. + */ + if (as->as_type == ASR_GETNETBYNAME && e->n_net == 0) { + /* XXX wrong */ + freenetent(e); + async_set_state(as, ASR_STATE_NEXT_DB); + } else { + ar->ar_h_errno = NETDB_SUCCESS; + ar->ar_netent = e; + async_set_state(as, ASR_STATE_HALT); + } + break; + + case ASR_STATE_NOT_FOUND: + + ar->ar_errno = 0; + ar->ar_h_errno = HOST_NOT_FOUND; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + if (ar->ar_h_errno) + 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; +} + +static int +netent_file_match(FILE *f, int type, const char *data, char **tokens, + int ntokens) +{ + int n, i; + in_addr_t net; + + for(;;) { + n = asr_parse_namedb_line(f, tokens, MAXTOKEN); + if (n == -1) + return (-1); + + if (type == ASR_GETNETBYADDR) { + net = inet_network(tokens[1]); + if (memcmp(&net, data, sizeof net) == 0) + return (n); + } else { + for (i = 0; i < n; i++) { + if (i == 1) + continue; + if (strcasecmp(data, tokens[i])) + continue; + return (n); + } + } + } +} + +static int +netent_from_packet(struct netent *n, int action, char *pkt, size_t pktlen) +{ + struct packed p; + struct header hdr; + struct query q; + struct rr rr; + int r; + + packed_init(&p, pkt, pktlen); + unpack_header(&p, &hdr); + for(; hdr.qdcount; hdr.qdcount--) + unpack_query(&p, &q); + + for(; hdr.ancount; hdr.ancount--) { + unpack_rr(&p, &rr); + if (rr.rr_class != C_IN) + continue; + switch (rr.rr_type) { + case T_CNAME: + if (action == ASR_GETNETBYNAME) + r = netent_add_alias(n, rr.rr_dname, 1); + else + r = netent_set_cname(n, rr.rr_dname, 1); + break; + case T_PTR: + if (action != ASR_GETNETBYADDR) + continue; + r = netent_set_cname(n, rr.rr.ptr.ptrname, 1); + /* XXX See if we need to have MULTI_PTRS_ARE_ALIASES */ + break; + case T_A: + if (n->n_addrtype != AF_INET) + break; + r = netent_set_cname(n, rr.rr_dname, 1); + n->n_net = ntohl(rr.rr.in_a.addr.s_addr); + break; + } + } + + return (0); +} + +static struct netent * +netent_alloc(int family) +{ + struct netent *n; + + n = calloc(1, sizeof *n); + if (n == NULL) + return (NULL); + + n->n_aliases = calloc(MAXALIASES, sizeof *n->n_aliases); + if (n->n_aliases == NULL) { + freenetent(n); + return (NULL); + } + n->n_addrtype = family; + + return (n); +} + +static int +netent_set_cname(struct netent *n, const char *name, int isdname) +{ + char buf[MAXDNAME]; + + if (n->n_name) + return (0); + + if (isdname) { + asr_strdname(name, buf, sizeof buf); + buf[strlen(buf) - 1] = '\0'; + n->n_name = strdup(buf); + } else { + n->n_name = strdup(name); + } + if (n->n_name == NULL) + return (-1); + + return (0); +} + +static int +netent_add_alias(struct netent *n, const char *name, int isdname) +{ + char buf[MAXDNAME]; + size_t i; + + for (i = 0; i < MAXALIASES; i++) + if (n->n_aliases[i] == NULL) + break; + if (i == MAXALIASES) + return (0); + + if (isdname) { + asr_strdname(name, buf, sizeof buf); + buf[strlen(buf)-1] = '\0'; + n->n_aliases[i] = strdup(buf); + } else { + n->n_aliases[i] = strdup(name); + } + if (n->n_aliases[i] == NULL) + return (-1); + + return (0); +} + +void +freenetent(struct netent *n) +{ + char **c; + + free(n->n_name); + for (c = n->n_aliases; *c; c++) + free(*c); + free(n->n_aliases); + free(n); +} diff --git a/lib/libc/asr/getrrsetbyname_async.c b/lib/libc/asr/getrrsetbyname_async.c new file mode 100644 index 00000000000..10707f8518c --- /dev/null +++ b/lib/libc/asr/getrrsetbyname_async.c @@ -0,0 +1,590 @@ +/* $OpenBSD: getrrsetbyname_async.c,v 1.1 2012/04/14 09:24:18 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/uio.h> + +#include <netinet/in.h> +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "asr_private.h" + +static int getrrsetbyname_async_run(struct async *, struct async_res *); +static void get_response(struct async_res *, const char *, int); + +struct async * +getrrsetbyname_async(const char *hostname, unsigned int rdclass, + unsigned int rdtype, unsigned int flags, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; + + ac = asr_use_resolver(asr); + if ((as = 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) + async_free(as); + + asr_ctx_unref(ac); + return (NULL); +} + +static int +getrrsetbyname_async_run(struct async *as, struct async_res *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.rrset.subq = res_query_async_ctx( + as->as.rrset.name, + as->as.rrset.class, + as->as.rrset.type, + NULL, 0, as->as_ctx); + if (as->as.rrset.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 ((async_run(as->as.rrset.subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + as->as.rrset.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_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = EAI_SYSTEM; + 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.1 2012/04/14 09:24:18 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 async_res *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/lib/libc/asr/hostaddr_async.c b/lib/libc/asr/hostaddr_async.c new file mode 100644 index 00000000000..ecea827186a --- /dev/null +++ b/lib/libc/asr/hostaddr_async.c @@ -0,0 +1,342 @@ +/* $OpenBSD: hostaddr_async.c,v 1.1 2012/04/14 09:24:18 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/uio.h> + +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "asr_private.h" + +static int hostaddr_async_run(struct async *, struct async_res *); +static int sockaddr_from_rr(struct sockaddr *, struct rr *); + +/* + * This API function allows to iterate over host addresses, for the given + * family which, must be AF_INET, AF_INET6, or AF_UNSPEC (in which case + * the family lookup list from the resolver is used). The strategy is to + * return all addresses found for the first DB that returned at least one + * address. + * + * Flags can be 0, or one of AI_CANONNAME or AI_FQDN. If set, the + * canonical name will be returned along with the address. + */ +struct async * +hostaddr_async_ctx(const char *name, int family, int flags, struct asr_ctx *ac) +{ + struct async *as; + char buf[MAXDNAME]; + +#ifdef DEBUG + asr_printf("asr: hostaddr_async_ctx(\"%s\", %i)\n", name, family); +#endif + if (asr_domcat(name, NULL, buf, sizeof buf) == 0) { + errno = EINVAL; + return (NULL); + } + + if ((as = async_new(ac, ASR_HOSTADDR)) == NULL) + goto err; /* errno set */ + as->as_run = hostaddr_async_run; + + as->as.host.aiflags = flags; + as->as.host.family = family; + as->as.host.name = strdup(buf); + if (as->as.host.name == NULL) + goto err; /* errno set */ + + return (as); + err: + if (as) + async_free(as); + return (NULL); +} + +static int +hostaddr_async_run(struct async *as, struct async_res *ar) +{ + struct packed p; + struct header h; + struct query q; + struct rr rr; + char buf[MAXDNAME], *c, *name; + int i, n, family, type, r; + + next: + switch(as->as_state) { + + case ASR_STATE_INIT: + + if (as->as.host.family != AF_INET && + as->as.host.family != AF_INET6 && + as->as.host.family != AF_UNSPEC) { + ar->ar_gai_errno = EAI_FAMILY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + as->as_count = 0; + as->as_db_idx = 0; + async_set_state(as, ASR_STATE_NEXT_DB); + break; + + case ASR_STATE_NEXT_FAMILY: + + as->as_family_idx += 1; + if (as->as.host.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_errno = 0; + ar->ar_gai_errno = 0; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + async_set_state(as, ASR_STATE_LOOKUP_FAMILY); + break; + + case ASR_STATE_LOOKUP_FAMILY: + + async_set_state(as, ASR_STATE_SAME_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; + /* FALLTHROUGH */ + + case ASR_STATE_SAME_DB: + + /* Query the current DB again. */ + + switch(AS_DB(as)) { + case ASR_DB_DNS: + + family = (as->as.host.family == AF_UNSPEC) ? + AS_FAMILY(as) : as->as.host.family; + type = (family == AF_INET6) ? T_AAAA : T_A; + name = as->as.host.name; + as->as.host.subq = res_query_async_ctx(name, C_IN, + type, NULL, 0, as->as_ctx); + if (as->as.host.subq == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + 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: + + as->as.host.file = fopen(as->as_ctx->ac_hostfile, "r"); + if (as->as.host.file == NULL) + async_set_state(as, ASR_STATE_NEXT_DB); + else + async_set_state(as, ASR_STATE_READ_FILE); + break; + + default: + async_set_state(as, ASR_STATE_NEXT_DB); + } + break; + + case ASR_STATE_SUBQUERY: + if ((r = async_run(as->as.host.subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + as->as.host.subq = NULL; + + if (ar->ar_datalen == -1) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + as->as.host.pkt = ar->ar_data; + as->as.host.pktlen = ar->ar_datalen; + packed_init(&p, as->as.host.pkt, as->as.host.pktlen); + unpack_header(&p, &h); + for(; h.qdcount; h.qdcount--) + unpack_query(&p, &q); + as->as.host.pktpos = p.offset; + as->as.host.ancount = h.ancount; + as->as.host.class = q.q_class; + as->as.host.type = q.q_type; + async_set_state(as, ASR_STATE_READ_RR); + break; + + case ASR_STATE_READ_RR: + + /* When done with this NS, try with next family */ + if (as->as.host.ancount == 0) { + free(as->as.host.pkt); + as->as.host.pkt = NULL; + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } + + /* Continue reading the packet where we left it. */ + packed_init(&p, as->as.host.pkt, as->as.host.pktlen); + p.offset = as->as.host.pktpos; + unpack_rr(&p, &rr); + as->as.host.pktpos = p.offset; + as->as.host.ancount -= 1; + if (rr.rr_type == as->as.host.type && + rr.rr_class == as->as.host.class) { + as->as_count += 1; + ar->ar_count = as->as_count; + sockaddr_from_rr(&ar->ar_sa.sa, &rr); + if (as->as.host.aiflags & AI_CANONNAME) + c = asr_strdname(rr.rr_dname, buf, + sizeof buf); + else if (as->as.host.aiflags & AI_FQDN) { + strlcpy(buf, as->as.host.name, sizeof buf); + c = buf; + } else + c = NULL; + if (c) { + if (c[strlen(c) - 1] == '.') + c[strlen(c) - 1] = '\0'; + ar->ar_cname = strdup(c); + } else + ar->ar_cname = NULL; + return (ASYNC_YIELD); + } + break; + + case ASR_STATE_READ_FILE: + + /* When done with the file, try next family. */ + n = asr_parse_namedb_line(as->as.host.file, as->as.host.tokens, + MAXTOKEN); + if (n == -1) { + fclose(as->as.host.file); + as->as.host.file = NULL; + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } + + for (i = 1; i < n; i++) { + if (strcasecmp(as->as.host.name, + as->as.host.tokens[i])) + continue; + + family = as->as.host.family; + if (family == AF_UNSPEC) + family = AS_FAMILY(as); + + if (sockaddr_from_str(&ar->ar_sa.sa, family, + as->as.host.tokens[0]) == -1) + continue; + + if (as->as.host.aiflags & AI_CANONNAME) { + strlcpy(buf, as->as.host.tokens[1], + sizeof buf); + c = buf; + } else if (as->as.host.aiflags & AI_FQDN) { + strlcpy(buf, as->as.host.name, sizeof buf); + c = buf; + } else + c = NULL; + if (c) { + if (c[strlen(c) - 1] == '.') + c[strlen(c) - 1] = '\0'; + ar->ar_cname = strdup(c); + } else + ar->ar_cname = NULL; + as->as_count += 1; + ar->ar_count = as->as_count; + return (ASYNC_YIELD); + } + break; + + case ASR_STATE_NOT_FOUND: + /* XXX the exact error depends on what query/send returned */ + ar->ar_errno = 0; + ar->ar_gai_errno = EAI_NODATA; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + ar->ar_count = as->as_count; + if (ar->ar_count) { + ar->ar_errno = 0; + ar->ar_gai_errno = 0; + } + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +static int +sockaddr_from_rr(struct sockaddr *sa, struct rr *rr) +{ + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + if (rr->rr_class != C_IN) + return (-1); + + switch (rr->rr_type) { + case T_A: + sin = (struct sockaddr_in*)sa; + memset(sin, 0, sizeof *sin); + sin->sin_len = sizeof *sin; + sin->sin_family = PF_INET; + sin->sin_addr = rr->rr.in_a.addr; + sin->sin_port = 0; + return (0); + case T_AAAA: + sin6 = (struct sockaddr_in6*)sa; + memset(sin6, 0, sizeof *sin6); + sin6->sin6_len = sizeof *sin6; + sin6->sin6_family = PF_INET6; + sin6->sin6_addr = rr->rr.in_aaaa.addr6; + sin6->sin6_port = 0; + return (0); + + default: + break; + } + + return (-1); +} diff --git a/lib/libc/asr/res_search_async.c b/lib/libc/asr/res_search_async.c new file mode 100644 index 00000000000..99e315313e8 --- /dev/null +++ b/lib/libc/asr/res_search_async.c @@ -0,0 +1,224 @@ +/* $OpenBSD: res_search_async.c,v 1.1 2012/04/14 09:24:18 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/uio.h> + +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* + * TODO: + * + * - make it possible to reuse ibuf if it was NULL when first called, + * to avoid reallocating buffers everytime. + */ + +#include "asr.h" +#include "asr_private.h" + +static int res_search_async_run(struct async *, struct async_res *); + +/* + * Unlike res_query_async(), this function returns a valid packet only if + * h_errno is NETDB_SUCCESS. + */ +struct async * +res_search_async(const char *name, int class, int type, unsigned char *ans, + int anslen, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; +#ifdef DEBUG + asr_printf("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type); +#endif + ac = asr_use_resolver(asr); + as = res_search_async_ctx(name, class, type, ans, anslen, ac); + asr_ctx_unref(ac); + + return (as); +} + +struct async * +res_search_async_ctx(const char *name, int class, int type, unsigned char *ans, + int anslen, struct asr_ctx *ac) +{ + struct async *as; + +#ifdef DEBUG + asr_printf("asr: res_search_async_ctx(\"%s\", %i, %i)\n", + name, class, type); +#endif + + if ((as = 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 */ + + if (ans) { + as->as.search.flags |= ASYNC_EXTIBUF; + as->as.search.ibuf = ans; + as->as.search.ibufsize = anslen; + } else { + as->as.search.ibuf = NULL; + as->as.search.ibufsize = 0; + } + as->as.search.ibuflen = 0; + + as->as.search.class = class; + as->as.search.type = type; + + return (as); + err: + if (as) + async_free(as); + return (NULL); +} + +#define HERRNO_UNSET -2 + +static int +res_search_async_run(struct async *as, struct async_res *ar) +{ + int r; + char fqdn[MAXDNAME]; + + next: + switch(as->as_state) { + + case ASR_STATE_INIT: + + 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 > sizeof(fqdn)) { + 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.search.subq = res_query_async_ctx(fqdn, + as->as.search.class, as->as.search.type, + as->as.search.ibuf, as->as.search.ibufsize, as->as_ctx); + if (as->as.search.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 = async_run(as->as.search.subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + as->as.search.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; + } + + /* + * If we don't use an external buffer, the packet was allocated + * by the subquery and it must be freed now. + */ + if ((as->as.search.flags & ASYNC_EXTIBUF) == 0) + free(ar->ar_data); + + /* + * The original resolver does something like this, to + */ + if (as->as_dom_flags & (ASYNC_DOM_NDOTS | ASYNC_DOM_ASIS)) + 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.search.flags |= ASYNC_NODATA; + else if (ar->ar_h_errno == TRY_AGAIN) + as->as.search.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.search.flags & ASYNC_NODATA) + ar->ar_h_errno = NO_DATA; + else if (as->as.search.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; +} diff --git a/lib/libc/asr/res_send_async.c b/lib/libc/asr/res_send_async.c new file mode 100644 index 00000000000..3516c685ad2 --- /dev/null +++ b/lib/libc/asr/res_send_async.c @@ -0,0 +1,809 @@ +/* $OpenBSD: res_send_async.c,v 1.1 2012/04/14 09:24:18 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/uio.h> + +#include <netinet/in.h> +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <resolv.h> /* for res_random */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr.h" +#include "asr_private.h" + +#define OP_QUERY (0) + +static int res_send_async_run(struct async *, struct async_res *); +static int sockaddr_connect(const struct sockaddr *, int); +static int udp_send(struct async *); +static int udp_recv(struct async *); +static int tcp_write(struct async *); +static int tcp_read(struct async *); +static int validate_packet(struct async *); +static int setup_query(struct async *, const char *, const char *, int, int); +static int ensure_ibuf(struct async *, size_t); + +#ifdef DEBUG +char *print_addr(const struct sockaddr *, char *, size_t); +#endif + +#define AS_NS_SA(p) ((p)->as_ctx->ac_ns[(p)->as_ns_idx - 1]) + + +struct async * +res_send_async(const unsigned char *buf, int buflen, unsigned char *ans, + int anslen, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; + struct packed p; + struct header h; + struct query q; + +#ifdef DEBUG + if (asr_debug) { + asr_printf("asr: res_send_async()\n"); + asr_dump_packet(stderr, buf, buflen, 0); + } +#endif + ac = asr_use_resolver(asr); + if ((as = async_new(ac, ASR_SEND)) == NULL) { + asr_ctx_unref(ac); + return (NULL); /* errno set */ + } + as->as_run = res_send_async_run; + + if (ans) { + as->as.dns.flags |= ASYNC_EXTIBUF; + as->as.dns.ibuf = ans; + as->as.dns.ibufsize = anslen; + as->as.dns.ibuflen = 0; + } else { + as->as.dns.ibuf = NULL; + as->as.dns.ibufsize = 0; + as->as.dns.ibuflen = 0; + } + + as->as.dns.flags |= ASYNC_EXTOBUF; + as->as.dns.obuf = (unsigned char*)buf; + as->as.dns.obuflen = buflen; + as->as.dns.obufsize = buflen; + + packed_init(&p, (char*)buf, buflen); + unpack_header(&p, &h); + 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) + async_free(as); + asr_ctx_unref(ac); + return (NULL); +} + +/* + * 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 + * (ans == NULL). + */ +struct async * +res_query_async(const char *name, int class, int type, unsigned char *ans, + int anslen, struct asr *asr) +{ + struct asr_ctx *ac; + struct async *as; +#ifdef DEBUG + asr_printf("asr: res_query_async(\"%s\", %i, %i)\n", name, class, type); +#endif + ac = asr_use_resolver(asr); + as = res_query_async_ctx(name, class, type, ans, anslen, ac); + asr_ctx_unref(ac); + + return (as); +} + +struct async * +res_query_async_ctx(const char *name, int class, int type, unsigned char *ans, + int anslen, struct asr_ctx *a_ctx) +{ + struct async *as; + +#ifdef DEBUG + asr_printf("asr: res_query_async_ctx(\"%s\", %i, %i)\n", name, class, + type); +#endif + if ((as = async_new(a_ctx, ASR_SEND)) == NULL) + return (NULL); /* errno set */ + as->as_run = res_send_async_run; + + if (ans) { + as->as.dns.flags |= ASYNC_EXTIBUF; + as->as.dns.ibuf = ans; + as->as.dns.ibufsize = anslen; + } else { + as->as.dns.ibuf = NULL; + as->as.dns.ibufsize = 0; + } + as->as.dns.ibuflen = 0; + + /* This adds a "." to name if it doesn't already has one. + * That's how res_query() behaves (trough res_mkquery"). + */ + if (setup_query(as, name, NULL, class, type) == -1) + goto err; /* errno set */ + + return (as); + + err: + if (as) + async_free(as); + + return (NULL); +} + +static int +res_send_async_run(struct async *as, struct async_res *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 (asr_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 = ASYNC_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 = ASYNC_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + case 1: + ar->ar_cond = ASYNC_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 = ASYNC_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + } + break; + + case ASR_STATE_PACKET: + + memmove(&ar->ar_sa.sa, AS_NS_SA(as), AS_NS_SA(as)->sa_len); + 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, flags, sock; + + 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->sa_len) == -1) { + 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 async *as, const char *name, const char *dom, + int class, int type) +{ + struct packed p; + struct header h; + char fqdn[MAXDNAME]; + char dname[MAXDNAME]; + + if (as->as.dns.flags & ASYNC_EXTOBUF) { + errno = EINVAL; +#ifdef DEBUG + asr_printf("attempting to write in user packet"); +#endif + return (-1); + } + + if (asr_make_fqdn(name, dom, fqdn, sizeof(fqdn)) > sizeof(fqdn)) { + errno = EINVAL; +#ifdef DEBUG + asr_printf("asr_make_fqdn: name too long\n"); +#endif + return (-1); + } + + if (dname_from_fqdn(fqdn, dname, sizeof(dname)) == -1) { + errno = EINVAL; +#ifdef DEBUG + asr_printf("dname_from_fqdn: invalid\n"); +#endif + 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; + h.qdcount = 1; + + packed_init(&p, as->as.dns.obuf, as->as.dns.obufsize); + pack_header(&p, &h); + pack_query(&p, type, class, dname); + if (p.err) { +#ifdef DEBUG + asr_printf("error packing query"); +#endif + 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) { +#ifdef DEBUG + asr_printf("strdup"); +#endif + return (-1); /* errno set */ + } + as->as.dns.obuflen = p.offset; + +#ifdef DEBUG + if (asr_debug) { + asr_printf("------- asr_setup_query(): packet -------\n"); + asr_dump_packet(stderr, as->as.dns.obuf, as->as.dns.obuflen, 0); + asr_printf("-----------------------------------------\n"); + } +#endif + + 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 async *as) +{ + ssize_t n; + int save_errno; +#ifdef DEBUG + char buf[256]; + + if (asr_debug) + asr_printf("asr: [%p] connecting to %s UDP\n", as, + print_addr(AS_NS_SA(as), buf, sizeof buf)); +#endif + as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_DGRAM); + if (as->as_fd == -1) + return (-1); /* errno set */ + + as->as_timeout = as->as_ctx->ac_nstimeout; + + 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 async *as) +{ + ssize_t n; + int save_errno; + + /* Allocate input buf if needed */ + if (as->as.dns.ibuf == NULL) { + if (ensure_ibuf(as, PACKETSZ) == -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; + +#ifdef DEBUG + if (asr_debug) { + asr_printf("------- asr_udp_recv() packet -------\n"); + asr_dump_packet(stderr, as->as.dns.ibuf, as->as.dns.ibuflen, 0); + asr_printf("-------------------------------------\n"); + } +#endif + + 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 async *as) +{ + struct iovec iov[2]; + uint16_t len; + ssize_t n; + int i, se; + socklen_t sl; +#ifdef DEBUG + char buf[256]; +#endif + + /* First try to connect if not already */ + if (as->as_fd == -1) { +#ifdef DEBUG + if (asr_debug) + asr_printf("asr: [%p] connecting to %s TCP\n", as, + print_addr(AS_NS_SA(as), buf, sizeof buf)); +#endif + as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_STREAM); + if (as->as_fd == -1) + return (-1); /* errno set */ + as->as_timeout = as->as_ctx->ac_nstimeout; + return (1); + } + + i = 0; + + /* Check if the connection succeeded. */ + if (as->as.dns.datalen == 0) { + sl = sizeof(se); + if (getsockopt(as->as_fd, SOL_SOCKET, SO_ERROR, &se, &sl) == -1) + goto close; /* errno set */ + if (se) { + errno = se; + goto close; + } + + as->as.dns.bufpos = 0; + + /* Send the packet length first */ + len = htons(as->as.dns.obuflen); + iov[i].iov_base = &len; + iov[i].iov_len = sizeof(len); + i++; + } + + iov[i].iov_base = as->as.dns.obuf + as->as.dns.bufpos; + iov[i].iov_len = as->as.dns.obuflen - as->as.dns.bufpos; + i++; + + n = writev(as->as_fd, iov, i); + if (n == -1) + goto close; /* errno set */ + + /* + * We want at least the packet length to be written out the first time. + * Technically we could recover but that makes little sense to support + * that. + */ + if (as->as.dns.datalen == 0 && n < 2) { + errno = EIO; + goto close; + } + + if (as->as.dns.datalen == 0) { + as->as.dns.datalen = len; + n -= 2; + } + + as->as.dns.bufpos += n; + if (as->as.dns.bufpos == as->as.dns.obuflen) { + /* All sent. Prepare for TCP read */ + as->as.dns.datalen = 0; + return (0); + } + + /* More data to write */ + as->as_timeout = as->as_ctx->ac_nstimeout; + 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 async *as) +{ + uint16_t len; + ssize_t n; + int save_errno; + + /* We must read the packet len first */ + if (as->as.dns.datalen == 0) { + n = read(as->as_fd, &len, sizeof(len)); + if (n == -1) + goto close; /* errno set */ + /* + * If the server has sent us only the first byte, we fail. + * Technically, we could recover but it might not be worth + * supporting that. + */ + if (n < 2) { + errno = EIO; + goto close; + } + + as->as.dns.datalen = ntohs(len); + as->as.dns.bufpos = 0; + as->as.dns.ibuflen = 0; + + if (ensure_ibuf(as, as->as.dns.datalen) == -1) + goto close; /* errno set */ + } + + n = read(as->as_fd, as->as.dns.ibuf + as->as.dns.ibuflen, + as->as.dns.datalen - as->as.dns.ibuflen); + if (n == -1) + goto close; /* errno set */ + if (n == 0) { + errno = ECONNRESET; + goto close; + } + as->as.dns.ibuflen += n; + + /* See if we got all the advertised bytes. */ + if (as->as.dns.ibuflen != as->as.dns.datalen) + return (1); + +#ifdef DEBUG + if (asr_debug) { + asr_printf("------- asr_tcp_read() packet -------\n"); + asr_dump_packet(stderr, as->as.dns.ibuf, as->as.dns.ibuflen, 0); + asr_printf("-------------------------------------\n"); + } +#endif + 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. + * If not (or not allocated) allocated enough space, unless the + * buffer is external (owned by the caller), in which case it fails. + */ +static int +ensure_ibuf(struct async *as, size_t n) +{ + char *t; + + if (as->as.dns.flags & ASYNC_EXTIBUF) { + if (n <= as->as.dns.ibufsize) + return (0); + errno = EINVAL; + return (-1); + } + + if (as->as.dns.ibuf == NULL) { + as->as.dns.ibuf = malloc(n); + if (as->as.dns.ibuf == NULL) + return (-1); /* errno set */ + as->as.dns.ibufsize = n; + return (0); + } + + if (as->as.dns.ibufsize >= n) + return (0); + + t = realloc(as->as.dns.ibuf, n); + 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 async *as) +{ + struct packed p; + struct header h; + struct query q; + struct rr rr; + int r; + + packed_init(&p, as->as.dns.ibuf, as->as.dns.ibuflen); + + unpack_header(&p, &h); + if (p.err) + goto inval; + + if (h.id != as->as.dns.reqid) { +#ifdef DEBUG + asr_printf("incorrect reqid\n"); +#endif + 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; + + 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)) { +#ifdef DEBUG + asr_printf("incorrect type/class/dname '%s' != '%s'\n", + q.q_dname, as->as.dns.dname); +#endif + goto inval; + } + + /* Check for truncation */ + if (h.flags & TC_MASK) { + errno = EOVERFLOW; + return (-1); + } + + /* Validate the rest of the packet */ + for(r = h.ancount + h.nscount + h.arcount; r; r--) + unpack_rr(&p, &rr); + + if (p.err || (p.offset != as->as.dns.ibuflen)) + goto inval; + + return (0); + + inval: + errno = EINVAL; + return (-1); +} |