summaryrefslogtreecommitdiff
path: root/usr.sbin/smtpd/asr.c
diff options
context:
space:
mode:
authorGilles Chehade <gilles@cvs.openbsd.org>2010-11-29 15:25:57 +0000
committerGilles Chehade <gilles@cvs.openbsd.org>2010-11-29 15:25:57 +0000
commit71b46ba7782a17d5727aef137f7891f6c027471f (patch)
treebd40aa6c1666b74792d58e2bf86604345ccec3f9 /usr.sbin/smtpd/asr.c
parent369cb22a97518580876a431f325eae15f50be5df (diff)
replace the fork-based-non-blocking-resolver-hack by shiny async resolver
written by eric@. it is still experimental but still better than what we had earlier so ... we'll improve in tree :) diff by me with *lots* of help from eric@, tested by todd and I (and a few people out there)
Diffstat (limited to 'usr.sbin/smtpd/asr.c')
-rw-r--r--usr.sbin/smtpd/asr.c2233
1 files changed, 2233 insertions, 0 deletions
diff --git a/usr.sbin/smtpd/asr.c b/usr.sbin/smtpd/asr.c
new file mode 100644
index 00000000000..0a878a7ec5b
--- /dev/null
+++ b/usr.sbin/smtpd/asr.c
@@ -0,0 +1,2233 @@
+/*
+ * Copyright (c) 2010 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 <sys/stat.h>
+#include <sys/uio.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asr.h"
+#include "dnsutil.h"
+
+#define unused __attribute__ ((unused))
+
+#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 ASR_MAXNS 5
+#define ASR_MAXDB 3
+#define ASR_MAXDOM 10
+
+enum asr_query_type {
+ ASR_QUERY_DNS,
+ ASR_QUERY_HOST,
+ ASR_QUERY_ADDRINFO
+};
+
+enum asr_db_type {
+ ASR_DB_FILE,
+ ASR_DB_DNS,
+ ASR_DB_YP,
+};
+struct asr_db {
+ int ad_type;
+ char *ad_path;
+ int ad_timeout;
+ int ad_retries;
+ int ad_count;
+ struct sockaddr *ad_sa[ASR_MAXNS];
+};
+
+struct asr_ctx {
+ int ac_refcount;
+ int ac_ndots;
+ int ac_forcetcp;
+ char *ac_domain;
+ int ac_domcount;
+ char *ac_dom[ASR_MAXDOM];
+ int ac_dbcount;
+ struct asr_db ac_db[ASR_MAXDB];
+ int ac_family[3];
+};
+
+struct asr {
+ char *a_path;
+ time_t a_mtime;
+ struct asr_ctx *a_ctx;
+};
+
+struct asr_query {
+
+ struct asr_ctx *aq_ctx;
+ int aq_type;
+ int aq_flags;
+ int aq_state;
+
+ int aq_timeout;
+ int aq_fd;
+
+ int aq_dom_idx;
+ int aq_family_idx;
+ int aq_db_idx;
+ int aq_ns_idx;
+ int aq_ns_cycles;
+ /* for dns */
+ char *aq_fqdn; /* the fqdn being looked for */
+ struct query aq_query;
+ uint16_t aq_reqid;
+ char *aq_buf;
+ size_t aq_buflen;
+ size_t aq_bufsize;
+ size_t aq_bufoffset; /* for TCP */
+ uint16_t aq_datalen; /* for TCP */
+ struct packed aq_packed;
+ int aq_nanswer;
+
+ /* for host */
+ char *aq_host;
+ int aq_family;
+ int aq_count;
+ FILE *aq_file;
+
+ /* for addrinfo */
+ char *aq_hostname;
+ char *aq_servname;
+ struct addrinfo aq_hints;
+ struct asr_query *aq_subq;
+ struct addrinfo *aq_aifirst;
+ struct addrinfo *aq_ailast;
+};
+
+#define AQ_FAMILY(p) ((p)->aq_ctx->ac_family[(p)->aq_family_idx])
+#define AQ_DB(p) (&((p)->aq_ctx->ac_db[(p)->aq_db_idx]))
+#define AQ_NS_SA(p) (AQ_DB(p)->ad_sa[(p)->aq_ns_idx])
+#define AQ_BUF_LEFT(p) ((p)->aq_bufsize - (p)->aq_buflen)
+#define AQ_BUF_DATA(p) ((p)->aq_buf + (p)->aq_bufoffset)
+#define AQ_BUF_LEN(p) ((p)->aq_buflen - (p)->aq_bufoffset)
+#define AQ_BUF_WPOS(p) ((p)->aq_buf + (p)->aq_buflen)
+
+enum asr_state {
+ ASR_STATE_INIT,
+ ASR_STATE_NEXT_DOMAIN,
+ ASR_STATE_SEARCH_DOMAIN,
+ ASR_STATE_NEXT_DB,
+ ASR_STATE_QUERY_DB,
+ ASR_STATE_NEXT_FAMILY,
+ ASR_STATE_LOOKUP_FAMILY,
+ ASR_STATE_NEXT_NS,
+ ASR_STATE_QUERY_NS,
+ ASR_STATE_READ_RR,
+ ASR_STATE_QUERY_FILE,
+ 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_NEXT_MATCH,
+ ASR_STATE_TRY_MATCH,
+ ASR_STATE_SUBQUERY,
+ ASR_STATE_HALT,
+};
+
+/* misc utility functions */
+
+int asr_ndots(const char *);
+int asr_is_fqdn(const char *);
+int asr_cmp_fqdn_name(const char*, char*);
+char *asr_make_fqdn(const char *, const char *);
+int asr_parse_nameserver(struct sockaddr *, const char *);
+
+/* query functions */
+int asr_run_dns(struct asr_query *, struct asr_result *);
+int asr_run_host(struct asr_query *, struct asr_result *);
+int asr_run_addrinfo(struct asr_query *, struct asr_result *);
+
+/* a few helpers */
+const char * asr_error(int);
+
+void asr_check_reload(struct asr *);
+void asr_query_free(struct asr_query *);
+int asr_iter_family(struct asr_query *, int);
+int asr_ensure_buf(struct asr_query *, size_t);
+int asr_setup_packet(struct asr_query *);
+int asr_validate_packet(struct asr_query *);
+int asr_udp_send(struct asr_query *);
+int asr_udp_recv(struct asr_query *);
+int asr_tcp_write(struct asr_query *);
+int asr_tcp_read(struct asr_query *);
+int asr_parse_hosts_cb(char **, int, void*, void*);
+int asr_parse_namedb_line(FILE *, char **, int);
+int asr_get_port(const char *, const char *, int);
+int asr_add_sockaddr(struct asr_query *, struct sockaddr *);
+int asr_add_sockaddr2(struct asr_query *, struct sockaddr *, int, int);
+int asr_db_add_nameserver(struct asr_db *, const char *);
+void asr_db_done(struct asr_db *);
+
+struct asr_ctx *asr_ctx_create(void);
+int asr_ctx_unref(struct asr_ctx *);
+int asr_ctx_add_searchdomain(struct asr_ctx *, const char *);
+int asr_ctx_from_file(struct asr_ctx *, const char *);
+int asr_ctx_from_string(struct asr_ctx *, const char *);
+int asr_ctx_parse_cb(const char *,
+ int (*)(char**, int, void*, void*),
+ void *, void *);
+struct asr_query *asr_ctx_query(struct asr_ctx *, int);
+struct asr_query *asr_ctx_query_host(struct asr_ctx *, const char *, int);
+
+#ifdef ASR_DEBUG
+
+void asr_dump(struct asr *);
+void asr_dump_query(struct asr_query *);
+
+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, j;
+ struct asr_db *ad;
+ struct asr_ctx *ac;
+
+ ac = a->a_ctx;
+
+ printf("--------- ASR CONFIG ---------------\n");
+ printf("DOMAIN \"%s\"\n", ac->ac_domain);
+ printf("SEARCH\n");
+ for(i = 0; i < ac->ac_domcount; i++)
+ printf(" \"%s\"\n", ac->ac_dom[i]);
+ printf("OPTIONS\n");
+ printf(" forcetcp: %i\n", ac->ac_forcetcp);
+ printf(" ndots: %i\n", ac->ac_ndots);
+ printf(" family: ");
+ for(i = 0; ac->ac_family[i] != -1; i++)
+ printf(" %s", (ac->ac_family[i] == AF_INET) ? "inet" : "inet6");
+ printf("\n");
+ printf("DB\n");
+ for(ad = ac->ac_db, i = 0; i < ac->ac_dbcount; i++, ad++) {
+ switch (ad->ad_type) {
+ case ASR_DB_FILE:
+ printf(" FILE \"%s\"\n", ad->ad_path);
+ break;
+ case ASR_DB_DNS:
+ printf(" DNS timeout %ims, retries %i\n",
+ ad->ad_timeout,
+ ad->ad_retries);
+ for(j = 0; j < ad->ad_count; j++)
+ printf(" NS %s\n",
+ print_addr(ad->ad_sa[j], buf,
+ sizeof buf));
+ break;
+ case ASR_DB_YP:
+ printf(" YP\n");
+ break;
+ default:
+ printf(" - ???? %i\n", ad->ad_type);
+ }
+ }
+ printf("------------------------------------\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_QUERY_DNS, "ASR_QUERY_DNS" },
+ { ASR_QUERY_HOST, "ASR_QUERY_HOST" },
+ { ASR_QUERY_ADDRINFO, "ASR_QUERY_ADDRINFO" },
+ { 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_NEXT_DOMAIN, "ASR_STATE_NEXT_DOMAIN" },
+ { ASR_STATE_SEARCH_DOMAIN, "ASR_STATE_SEARCH_DOMAIN" },
+ { ASR_STATE_NEXT_DB, "ASR_STATE_NEXT_DB" },
+ { ASR_STATE_QUERY_DB, "ASR_STATE_QUERY_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_QUERY_NS, "ASR_STATE_QUERY_NS" },
+ { ASR_STATE_READ_RR, "ASR_STATE_READ_RR" },
+ { ASR_STATE_QUERY_FILE, "ASR_STATE_QUERY_FILE" },
+ { 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_NEXT_MATCH, "ASR_STATE_NEXT_MATCH" },
+ { ASR_STATE_TRY_MATCH, "ASR_STATE_TRY_MATCH" },
+ { ASR_STATE_SUBQUERY, "ASR_STATE_SUBQUERY" },
+ { ASR_STATE_HALT, "ASR_STATE_HALT" },
+ { 0, NULL }
+};
+
+struct kv kv_transition[] = {
+ { ASR_NEED_READ, "ASR_NEED_READ" },
+ { ASR_NEED_WRITE, "ASR_NEED_WRITE" },
+ { ASR_YIELD, "ASR_YIELD" },
+ { ASR_DONE, "ASR_DONE" },
+ { 0, NULL }
+};
+
+void
+asr_dump_query(struct asr_query *aq)
+{
+ printf("%-25s fqdn=%s dom %-2i fam %-2i famidx %-2i db %-2i ns %-2i ns_cycles %-2i fd %-2i %ims",
+ kvlookup(kv_state, aq->aq_state),
+ aq->aq_fqdn,
+ aq->aq_dom_idx,
+ aq->aq_family,
+ aq->aq_family_idx,
+ aq->aq_db_idx,
+ aq->aq_ns_idx,
+ aq->aq_ns_cycles,
+ aq->aq_fd,
+ aq->aq_timeout);
+ printf("\n");
+}
+
+#endif /* ASR_DEBUG */
+
+struct asr *
+asr_resolver(const char *conf)
+{
+ int r;
+ struct asr *asr;
+
+#ifdef ASR_DEBUG
+ if (asr_debug == 0)
+ if(getenv("ASR_DEBUG")) {
+ printf("asr: %zu\n", sizeof(struct asr));
+ printf("asr_ctx: %zu\n", sizeof(struct asr_ctx));
+ printf("asr_db: %zu\n", sizeof(struct asr_db));
+ printf("asr_query: %zu\n", sizeof(struct asr_query));
+ printf("asr_result: %zu\n", sizeof(struct asr_result));
+ asr_debug = 1;
+ }
+#endif
+ if ((asr = calloc(1, sizeof(*asr))) == NULL)
+ return (NULL);
+
+ if ((asr->a_ctx = asr_ctx_create()) == NULL) {
+ free(asr);
+ return (NULL);
+ }
+
+ if (conf == NULL)
+ conf = DEFAULT_CONFFILE;
+
+ if (conf[0] == '!') {
+ r = asr_ctx_from_string(asr->a_ctx, conf + 1);
+ } else {
+ r = 0;
+ asr->a_path = strdup(conf);
+ asr_check_reload(asr);
+ if (asr->a_ctx == NULL)
+ r = asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF);
+ }
+
+ if (r == -1) {
+ asr_ctx_unref(asr->a_ctx);
+ free(asr);
+ return (NULL);
+ }
+
+#ifdef ASR_DEBUG
+ if (asr_debug)
+ asr_dump(asr);
+#endif
+
+ return (asr);
+}
+
+void
+asr_abort(struct asr_query *aq)
+{
+ asr_query_free(aq);
+}
+
+int
+asr_run(struct asr_query *aq, struct asr_result *ar)
+{
+ int r;
+
+#ifdef ASR_DEBUG
+ if (asr_debug) {
+ printf("-> QUERY %p(%p) %s\n",
+ aq, aq->aq_ctx,
+ kvlookup(kv_query_type, aq->aq_type));
+ }
+#endif
+
+ switch(aq->aq_type) {
+ case ASR_QUERY_DNS:
+ r = asr_run_dns(aq, ar);
+ break;
+ case ASR_QUERY_HOST:
+ r = asr_run_host(aq, ar);
+ break;
+ case ASR_QUERY_ADDRINFO:
+ r = asr_run_addrinfo(aq, ar);
+ break;
+ default:
+ ar->ar_err = EOPNOTSUPP;
+ ar->ar_errstr = "unknown query type";
+ r = ASR_DONE;
+ }
+#ifdef ASR_DEBUG
+ if (asr_debug) {
+ printf("<- ");
+ asr_dump_query(aq);
+ printf(" = %s\n", kvlookup(kv_transition, r));
+ }
+#endif
+ if (r == ASR_DONE)
+ asr_query_free(aq);
+
+ return (r);
+}
+
+int
+asr_run_sync(struct asr_query *aq, struct asr_result *ar)
+{
+ struct pollfd fds[1];
+ int r;
+
+ for(;;) {
+ r = asr_run(aq, ar);
+ if (r == ASR_DONE || r == ASR_YIELD)
+ break;
+ fds[0].fd = ar->ar_fd;
+ fds[0].events = (r == ASR_NEED_READ) ? POLLIN : POLLOUT;
+ again:
+ r = poll(fds, 1, ar->ar_timeout);
+ if (r == -1 && errno == EINTR)
+ goto again;
+ if (r == -1) /* impossible? */
+ err(1, "poll");
+ }
+
+ return r;
+}
+
+void
+asr_check_reload(struct asr *asr)
+{
+ struct stat st;
+ struct asr_ctx *ac;
+
+ if (asr->a_path == NULL)
+ return;
+
+ if (stat(asr->a_path, &st) == -1)
+ return;
+
+ if (asr->a_mtime == st.st_mtime)
+ return;
+
+ if ((ac = asr_ctx_create()) == NULL)
+ return;
+
+ asr->a_mtime = st.st_mtime;
+
+ if (asr_ctx_from_file(ac, asr->a_path) == -1) {
+ asr_ctx_unref(ac);
+ return;
+ }
+
+ if (asr->a_ctx)
+ asr_ctx_unref(asr->a_ctx);
+ asr->a_ctx = ac;
+}
+
+struct asr_ctx *
+asr_ctx_create(void)
+{
+ struct asr_ctx *ac;
+
+ if ((ac = calloc(1, sizeof(*ac))) == NULL)
+ return (NULL);
+
+ 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;
+
+ return (ac);
+}
+
+int
+asr_ctx_unref(struct asr_ctx *ac)
+{
+ int i;
+
+ ac->ac_refcount--;
+
+ if (ac->ac_refcount == 0) {
+ if (ac->ac_domain)
+ free(ac->ac_domain);
+
+ for(i = 0; i < ac->ac_dbcount; i++)
+ asr_db_done(&ac->ac_db[i]);
+
+ for(i = 0; i < ac->ac_domcount; i++)
+ free(ac->ac_dom[i]);
+
+ free(ac);
+ return (0);
+ }
+
+ return (ac->ac_refcount);
+}
+
+int
+asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain)
+{
+ if (ac->ac_domcount == ASR_MAXDOM)
+ return (-1);
+
+ if ((ac->ac_dom[ac->ac_domcount] = asr_make_fqdn(domain, NULL)) == NULL)
+ return (0);
+
+ ac->ac_domcount += 1;
+
+ return (1);
+}
+
+static int
+pass0(char **tok, int n, void *a0, void *a1)
+{
+ struct asr_ctx *ac = (struct asr_ctx*)a0;
+ struct asr_db *ad;
+ int *nscount = (int*)a1;
+ int i, j, d;
+ const char *e;
+
+ /* search for lookup, domain, family, options, and count nameservers */
+
+ if (!strcmp(tok[0], "nameserver")) {
+ *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, ad = ac->ac_db; i < n;
+ i++, ac->ac_dbcount++, ad++) {
+
+ if (!strcmp(tok[i], "yp")) {
+ ad->ad_type = ASR_DB_YP;
+
+ } else if (!strcmp(tok[i], "bind")) {
+ ad->ad_type = ASR_DB_DNS;
+ ad->ad_count = 0;
+ ad->ad_timeout = 1000;
+ ad->ad_retries = 3;
+
+ } else if (!strcmp(tok[i], "file")) {
+ ad->ad_type = ASR_DB_FILE;
+ ad->ad_path = strdup(DEFAULT_HOSTFILE);
+ } 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], "option")) {
+ for(i = 1; i < n; i++) {
+ if (!strcmp(tok[i], "tcp"))
+ ac->ac_forcetcp = 1;
+ 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);
+}
+
+static int
+pass1(char **tok, int n, void *a0, unused void *a1)
+{
+ struct asr_db *ad = (struct asr_db*) a0;
+
+ /* fill the DNS db with the specified nameservers */
+
+ if (!strcmp(tok[0], "nameserver")) {
+ if (n != 2)
+ return (0);
+ asr_db_add_nameserver(ad, tok[1]);
+ }
+ return (0);
+}
+
+int
+asr_ctx_from_string(struct asr_ctx *ac, const char *str)
+{
+ char buf[512], *ch;
+ struct asr_db *ad;
+ int i;
+ int nscount = 0;
+
+ asr_ctx_parse_cb(str, pass0, ac, &nscount);
+
+ if (ac->ac_dbcount == 0) {
+ /* no lookup directive */
+ asr_ctx_parse_cb(DEFAULT_LOOKUP, pass0, ac, &nscount);
+ }
+
+ ad = NULL;
+ for(i = 0; i < ac->ac_dbcount; i++)
+ if (ac->ac_db[i].ad_type == ASR_DB_DNS) {
+ ad = &ac->ac_db[i];
+ break;
+ }
+
+ if (nscount && ad)
+ asr_ctx_parse_cb(str, pass1, ad, NULL);
+
+ 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 (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);
+}
+
+int
+asr_ctx_from_file(struct asr_ctx *ac, const char *path)
+{
+ FILE *cf;
+ char buf[1024];
+ ssize_t r;
+
+ cf = fopen(path, "r");
+ if (cf == NULL)
+ return (-1);
+
+ /* XXX make sure we read the whole file */
+ r = fread(buf, 1, sizeof buf - 1, cf);
+ fclose(cf);
+ if (r == -1)
+ return (-1);
+ buf[r] = '\0';
+
+ return asr_ctx_from_string(ac, buf);
+}
+
+int
+asr_ctx_parse_cb(const char *str,
+ int (*cb)(char**, int, void*, void*),
+ void *arg0,
+ void *arg1)
+{
+ 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, arg0, arg1))
+ break;
+ }
+
+ return (0);
+}
+
+struct asr_query *
+asr_ctx_query(struct asr_ctx *ac, int type)
+{
+ struct asr_query *aq;
+
+ if ((aq = calloc(1, sizeof(*aq))) == NULL)
+ return (NULL);
+
+ ac->ac_refcount += 1;
+
+ aq->aq_ctx = ac;
+ aq->aq_fd = -1;
+ aq->aq_type = type;
+ aq->aq_state = ASR_STATE_INIT;
+
+ return (aq);
+}
+
+void
+asr_done(struct asr *asr)
+{
+ if (asr_ctx_unref(asr->a_ctx))
+ return;
+ if (asr->a_path)
+ free(asr->a_path);
+ free(asr);
+}
+
+int
+asr_parse_hosts_cb(char **tok, int n, void *a0, void *a1)
+{
+ struct asr_query *aq = (struct asr_query*) a0;
+ struct asr_result *ar = (struct asr_result*) a1;
+ int i;
+
+ for (i = 1; i < n; i++) {
+ if (strcmp(tok[i], aq->aq_host))
+ continue;
+ if (sockaddr_from_str(&ar->ar_sa.sa, aq->aq_family, tok[0]) == -1)
+ continue;
+ ar->ar_cname = strdup(tok[1]);
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * utility functions
+ */
+
+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);
+
+ sockaddr_set_port(sa, portno);
+
+ return (0);
+}
+
+int
+asr_db_add_nameserver(struct asr_db *ad, const char *nameserver)
+{
+ struct sockaddr_storage ss;
+
+ if (ad->ad_type != ASR_DB_DNS)
+ return (-1);
+
+ if (ad->ad_count == ASR_MAXNS)
+ return (-1);
+
+ if (asr_parse_nameserver((struct sockaddr*)&ss, nameserver))
+ return (-1);
+
+ if ((ad->ad_sa[ad->ad_count] = calloc(1, ss.ss_len)) == NULL)
+ return (0);
+
+ memmove(ad->ad_sa[ad->ad_count], &ss, ss.ss_len);
+ ad->ad_count += 1;
+
+ return (1);
+}
+
+void
+asr_db_done(struct asr_db *ad)
+{
+ int i;
+
+ switch(ad->ad_type) {
+ case ASR_DB_DNS:
+ for(i = 0; i < ad->ad_count; i++)
+ free(ad->ad_sa[i]);
+ break;
+
+ case ASR_DB_YP:
+ break;
+
+ case ASR_DB_FILE:
+ free(ad->ad_path);
+ break;
+ default:
+ errx(1, "asr_db_done: unknown db type");
+ }
+}
+
+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);
+}
+
+const char *
+asr_error(int v)
+{
+ switch(v) {
+ case ASR_OK:
+ return "no error";
+ case EASR_MEMORY:
+ return "out of memory";
+ case EASR_TIMEDOUT:
+ return "all nameservers timed out";
+ case EASR_NAMESERVER:
+ return "no nameserver specified";
+ case EASR_FAMILY:
+ return "invalid address family";
+ case EASR_NOTFOUND:
+ return "not found";
+ case EASR_NAME:
+ return "invalid domain name";
+ default:
+ return "unknown error code";
+ }
+}
+
+int
+asr_cmp_fqdn_name(const char *fqdn, char *name)
+{
+ int i;
+
+ /* compare a fqdn with a name that may not end with a dot */
+
+ for (i = 0; fqdn[i] && name[i]; i++)
+ if (fqdn[i] != name[i])
+ return (-1);
+
+ if (fqdn[i] == name[i])
+ return (0);
+
+ if (fqdn[i] == 0 || fqdn[i] != '.' || fqdn[i+1] != 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+asr_ndots(const char *s)
+{
+ int n;
+
+ for(n = 0; *s; s++)
+ if (*s == '.')
+ n += 1;
+
+ return (n);
+}
+
+int
+asr_is_fqdn(const char *name)
+{
+ size_t len;
+
+ len = strlen(name);
+ return (len > 0 && name[len -1] == '.');
+}
+
+char *
+asr_make_fqdn(const char *name, const char *domain)
+{
+ char *fqdn;
+ size_t len;
+
+ if (domain == NULL)
+ domain = ".";
+#ifdef ASR_DEBUG
+ else
+ if (!asr_is_fqdn(domain))
+ errx(1, "domain is not FQDN: %s", domain);
+#endif
+
+ len = strlen(name);
+ if (len == 0) {
+ fqdn = strdup(domain);
+ } else if (name[len - 1] != '.') {
+ if (domain[0] == '.')
+ domain += 1;
+ len += strlen(domain) + 2;
+ fqdn = malloc(len);
+ if (fqdn == NULL)
+ return (NULL);
+ strlcpy(fqdn, name, len);
+ strlcat(fqdn, ".", len);
+ strlcat(fqdn, domain, len);
+ } else {
+ fqdn = strdup(name);
+ }
+
+ return (fqdn);
+}
+
+void
+asr_query_free(struct asr_query *aq)
+{
+ if (aq->aq_aifirst)
+ freeaddrinfo(aq->aq_aifirst);
+ if (aq->aq_subq)
+ asr_abort(aq->aq_subq);
+ if (aq->aq_host)
+ free(aq->aq_host);
+ if (aq->aq_fqdn)
+ free(aq->aq_fqdn);
+ if (aq->aq_buf)
+ free(aq->aq_buf);
+ if (aq->aq_hostname)
+ free(aq->aq_hostname);
+ if (aq->aq_servname)
+ free(aq->aq_servname);
+ if (aq->aq_fd != -1)
+ close(aq->aq_fd);
+ asr_ctx_unref(aq->aq_ctx);
+ free(aq);
+}
+
+/*
+ * for asr_query_dns
+ */
+
+struct asr_query *
+asr_query_dns(struct asr *asr,
+ uint16_t type,
+ uint16_t class,
+ const char *name,
+ int flags)
+{
+ struct asr_query *aq;
+
+ asr_check_reload(asr);
+
+ if ((aq = asr_ctx_query(asr->a_ctx, ASR_QUERY_DNS)) == NULL)
+ return (NULL);
+
+ aq->aq_flags = flags;
+ aq->aq_query.q_type = type;
+ aq->aq_query.q_class = class;
+ aq->aq_fqdn = asr_make_fqdn(name, NULL);
+ if (aq->aq_fqdn == NULL)
+ goto abort;
+
+ return (aq);
+ abort:
+ asr_query_free(aq);
+ return (NULL);
+}
+
+int
+asr_setup_packet(struct asr_query *aq)
+{
+ struct packed p;
+ struct header h;
+
+ if (dname_from_fqdn(aq->aq_fqdn,
+ aq->aq_query.q_dname,
+ sizeof(aq->aq_query.q_dname)) == -1) {
+ return (-1);
+ }
+
+ aq->aq_reqid = res_randomid();
+
+ memset(&h, 0, sizeof h);
+ h.id = aq->aq_reqid;
+ if (!(aq->aq_flags & ASR_NOREC))
+ h.flags |= RD_MASK;
+ h.qdcount = 1;
+
+ if (aq->aq_buf == NULL) {
+ aq->aq_bufsize = PACKET_MAXLEN;
+ if ((aq->aq_buf = malloc(aq->aq_bufsize)) == NULL)
+ return (-2);
+ }
+ aq->aq_bufoffset = 0;
+
+ packed_init(&p, aq->aq_buf, aq->aq_bufsize);
+ pack_header(&p, &h);
+ pack_query(&p, aq->aq_query.q_type, aq->aq_query.q_class,
+ aq->aq_query.q_dname);
+ aq->aq_buflen = p.offset;
+
+ return (0);
+}
+
+int
+asr_ensure_buf(struct asr_query *aq, size_t n)
+{
+ char *t;
+
+ if (aq->aq_buf == NULL) {
+ aq->aq_buf = malloc(n);
+ if (aq->aq_buf == NULL)
+ return (-1);
+ aq->aq_bufsize = n;
+ return (0);
+ }
+
+ if (aq->aq_bufsize > n)
+ return (0);
+
+ t = realloc(aq->aq_buf, n);
+ if (t == NULL)
+ return (-1);
+ aq->aq_buf = t;
+ aq->aq_bufsize = n;
+
+ return (0);
+}
+
+int
+asr_validate_packet(struct asr_query *aq)
+{
+ struct packed p;
+ struct header h;
+ struct query q;
+ struct rr rr;
+ int r;
+
+ packed_init(&p, aq->aq_buf, aq->aq_buflen);
+
+ unpack_header(&p, &h);
+ if (p.err)
+ return (-1);
+ if (h.id != aq->aq_reqid)
+ return (-1);
+ if (h.qdcount != 1)
+ return (-1);
+ if ((h.flags & Z_MASK) != 0)
+ return (-1); /* should be zero, we could allow this */
+ if (h.flags & TC_MASK)
+ return (-2);
+ if (OPCODE(h.flags) != OP_QUERY)
+ return (-1); /* actually, it depends on the request */
+ if ((h.flags & QR_MASK) == 0)
+ return (-1); /* not a response */
+
+ unpack_query(&p, &q);
+ if (p.err)
+ return (-1);
+ if (q.q_type != aq->aq_query.q_type ||
+ q.q_class != aq->aq_query.q_class ||
+ strcasecmp(q.q_dname, aq->aq_query.q_dname))
+ 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 != aq->aq_buflen))
+ return (-1);
+
+ return (0);
+}
+
+int
+asr_udp_send(struct asr_query *aq)
+{
+ ssize_t n;
+
+ aq->aq_fd = sockaddr_connect(AQ_NS_SA(aq), SOCK_DGRAM);
+ if (aq->aq_fd == -1)
+ return (-1);
+
+ aq->aq_timeout = AQ_DB(aq)->ad_timeout;
+
+ n = send(aq->aq_fd, aq->aq_buf, aq->aq_buflen, 0);
+ if (n == -1) {
+ if (errno == EAGAIN)
+ return (-2); /* timeout */
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+asr_udp_recv(struct asr_query *aq)
+{
+ ssize_t n;
+
+ n = recv(aq->aq_fd, aq->aq_buf, aq->aq_bufsize, 0);
+ if (n == -1) {
+ if (errno == EAGAIN)
+ return (-2); /* timeout */
+ return (-1);
+ }
+
+ aq->aq_buflen = n;
+
+ switch (asr_validate_packet(aq)) {
+ case -2:
+ return (1); /* truncated */
+ case -1:
+ return (-1);
+ default:
+ break;
+ }
+
+ return (0);
+}
+
+int
+asr_tcp_write(struct asr_query *aq)
+{
+ struct iovec iov[2];
+ uint16_t len;
+ ssize_t n;
+ int i;
+ socklen_t sl;
+ int se;
+
+ if (aq->aq_fd == -1) { /* connect */
+ aq->aq_fd = sockaddr_connect(AQ_NS_SA(aq), SOCK_STREAM);
+ if (aq->aq_fd == -1)
+ return (-1);
+ aq->aq_timeout = AQ_DB(aq)->ad_timeout;
+ return (1);
+ }
+
+ i = 0;
+ if (aq->aq_datalen == 0) {
+ /* check connection first */
+ sl = sizeof(se);
+ if (getsockopt(aq->aq_fd, SOL_SOCKET, SO_ERROR, &se, &sl) == -1) {
+ warn("getsockopt");
+ return (-1);
+ }
+ if (se)
+ return -1;
+
+ /* need to send datalen first */
+ len = htons(aq->aq_buflen);
+ iov[i].iov_base = &len;
+ iov[i].iov_len = sizeof(len);
+ i++;
+ }
+
+ iov[i].iov_base = AQ_BUF_DATA(aq);
+ iov[i].iov_len = AQ_BUF_LEN(aq);
+ i++;
+
+ n = writev(aq->aq_fd, iov, i);
+ if (n == -1) {
+ if (errno == EAGAIN)
+ return (-2); /* timeout */
+ warn("writev");
+ return (-1);
+ }
+
+ if (aq->aq_datalen == 0 && n < 2) {
+ /* we want to write the data len */
+ warnx("short write");
+ return (-1);
+ }
+
+ if (aq->aq_datalen == 0) {
+ aq->aq_datalen = len;
+ n -= 2;
+ }
+
+ aq->aq_bufoffset += n;
+ if (aq->aq_bufoffset == aq->aq_buflen) {
+ aq->aq_datalen = 0;
+ return (0); /* all sent */
+ }
+
+ aq->aq_timeout = AQ_DB(aq)->ad_timeout;
+ return (1);
+}
+
+int
+asr_tcp_read(struct asr_query *aq)
+{
+ uint16_t len;
+ ssize_t n;
+
+ if (aq->aq_datalen == 0) {
+ n = read(aq->aq_fd, &len, sizeof(len));
+ if (n == -1) {
+ if (errno == EAGAIN) /* timeout */
+ return (-2);
+ return (-1);
+ }
+ if (n < 2) {
+ warnx("short read");
+ return (-1);
+ }
+ aq->aq_datalen = ntohs(len);
+ aq->aq_bufoffset = 0;
+ aq->aq_buflen = 0;
+
+ if (asr_ensure_buf(aq, aq->aq_datalen) == -1)
+ return (-3); /* ENOMEM */
+
+ return (1); /* need more data */
+ }
+
+ n = read(aq->aq_fd, AQ_BUF_WPOS(aq), AQ_BUF_LEFT(aq));
+ if (n == -1) {
+ if (errno == EAGAIN) /* timeout */
+ return (-2);
+ warn("read");
+ return (-1);
+ }
+ if (n == 0) {
+ warnx("closed");
+ return (-1);
+ }
+ aq->aq_buflen += n;
+
+ if (aq->aq_buflen != aq->aq_datalen)
+ return (1); /* need more data */
+
+ if (asr_validate_packet(aq) != 0)
+ return (-1);
+
+ return (0);
+}
+
+int
+asr_run_dns(struct asr_query *aq, struct asr_result *ar)
+{
+ for(;;) { /* block not indented on purpose */
+#ifdef ASR_DEBUG
+ if (asr_debug) {
+ printf(" ");
+ asr_dump_query(aq);
+ }
+#endif
+ switch(aq->aq_state) {
+
+ case ASR_STATE_INIT:
+ aq->aq_ns_cycles = -1;
+ aq->aq_db_idx = 0;
+ aq->aq_state = ASR_STATE_QUERY_DB;
+ break;
+
+ case ASR_STATE_NEXT_DB:
+ aq->aq_db_idx += 1;
+ aq->aq_state = ASR_STATE_QUERY_DB;
+ break;
+
+ case ASR_STATE_QUERY_DB:
+ if (aq->aq_db_idx >= aq->aq_ctx->ac_dbcount) {
+ if (aq->aq_ns_cycles == -1)
+ ar->ar_err = EASR_NAMESERVER;
+ else
+ ar->ar_err = EASR_TIMEDOUT;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+
+ if (AQ_DB(aq)->ad_type != ASR_DB_DNS) {
+ aq->aq_state = ASR_STATE_NEXT_DB;
+ break;
+ }
+ aq->aq_ns_cycles = 0;
+ aq->aq_ns_idx = 0;
+ aq->aq_state = ASR_STATE_QUERY_NS;
+ break;
+
+ case ASR_STATE_NEXT_NS:
+ /* close the current fd if any */
+ if (aq->aq_fd != -1) {
+ close(aq->aq_fd);
+ aq->aq_fd = -1;
+ }
+
+ aq->aq_ns_idx += 1;
+ if (aq->aq_ns_idx >= AQ_DB(aq)->ad_count) {
+ aq->aq_ns_idx = 0;
+ aq->aq_ns_cycles++;
+ }
+ if (aq->aq_ns_cycles >= AQ_DB(aq)->ad_retries) {
+ aq->aq_state = ASR_STATE_NEXT_DB;
+ break;
+ }
+ aq->aq_state = ASR_STATE_QUERY_NS;
+ break;
+
+ case ASR_STATE_QUERY_NS:
+ if (aq->aq_ns_idx >= AQ_DB(aq)->ad_count) {
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+ }
+ switch (asr_setup_packet(aq)) {
+ case -2:
+ ar->ar_err = EASR_MEMORY;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ case -1:
+ ar->ar_err = EASR_NAME;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ default:
+ break;
+ }
+ if (aq->aq_ctx->ac_forcetcp)
+ aq->aq_state = ASR_STATE_TCP_WRITE;
+ else
+ aq->aq_state = ASR_STATE_UDP_SEND;
+ break;
+
+ case ASR_STATE_UDP_SEND:
+ if (asr_udp_send(aq) == 0) {
+ aq->aq_state = ASR_STATE_UDP_RECV;
+ ar->ar_fd = aq->aq_fd;
+ ar->ar_timeout = aq->aq_timeout;
+ return (ASR_NEED_READ);
+ }
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+
+ case ASR_STATE_UDP_RECV:
+ switch (asr_udp_recv(aq)) {
+ case -2: /* timeout */
+ case -1: /* fail */
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+ case 0: /* done */
+ aq->aq_state = ASR_STATE_PACKET;
+ break;
+ case 1: /* truncated */
+ close(aq->aq_fd);
+ aq->aq_fd = -1;
+ aq->aq_state = ASR_STATE_TCP_WRITE;
+ break;
+ }
+ break;
+
+ case ASR_STATE_TCP_WRITE:
+ switch (asr_tcp_write(aq)) {
+ case -2: /* timeout */
+ case -1: /* fail */
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+ case 0:
+ aq->aq_state = ASR_STATE_TCP_READ;
+ ar->ar_fd = aq->aq_fd;
+ ar->ar_timeout = aq->aq_timeout;
+ return (ASR_NEED_READ);
+ case 1:
+ ar->ar_fd = aq->aq_fd;
+ ar->ar_timeout = aq->aq_timeout;
+ return (ASR_NEED_WRITE);
+ }
+ break;
+
+ case ASR_STATE_TCP_READ:
+ switch (asr_tcp_read(aq)) {
+ case -3:
+ aq->aq_state = ASR_STATE_HALT;
+ ar->ar_err = EASR_MEMORY;
+ break;
+ case -2: /* timeout */
+ case -1: /* fail */
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+ case 0:
+ aq->aq_state = ASR_STATE_PACKET;
+ break;
+ case 1:
+ ar->ar_fd = aq->aq_fd;
+ ar->ar_timeout = aq->aq_timeout;
+ return (ASR_NEED_READ);
+ }
+ break;
+
+ case ASR_STATE_PACKET:
+ memmove(&ar->ar_sa.sa, AQ_NS_SA(aq), AQ_NS_SA(aq)->sa_len);
+ ar->ar_datalen = aq->aq_buflen;
+ ar->ar_data = aq->aq_buf;
+ aq->aq_buf = NULL;
+ ar->ar_err = ASR_OK;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+
+ case ASR_STATE_HALT:
+ ar->ar_errstr = asr_error(ar->ar_err);
+ if (ar->ar_err)
+ ar->ar_data = NULL;
+ return (ASR_DONE);
+
+ default:
+ errx(1, "asr_run_dns: unknown state");
+ }}
+}
+
+/*
+ * for asr_query_host
+ */
+
+struct asr_query *
+asr_query_host(struct asr *asr, const char *host, int family)
+{
+ asr_check_reload(asr);
+
+ return asr_ctx_query_host(asr->a_ctx, host, family);
+}
+
+struct asr_query *
+asr_ctx_query_host(struct asr_ctx *ac, const char *host, int family)
+{
+ struct asr_query *aq;
+
+ if ((aq = asr_ctx_query(ac, ASR_QUERY_HOST)) == NULL)
+ return (NULL);
+
+ aq->aq_family = family;
+ aq->aq_host = strdup(host);
+ if (aq->aq_host)
+ return (aq);
+
+ asr_query_free(aq);
+ return (NULL);
+}
+
+int
+asr_run_host(struct asr_query *aq, struct asr_result *ar)
+{
+ struct header h;
+ struct query q;
+ struct rr rr;
+ char *tok[10];
+ int ntok = 10, i, n, family;
+
+ for(;;) { /* block not indented on purpose */
+#ifdef ASR_DEBUG
+ if (asr_debug) {
+ printf(" ");
+ asr_dump_query(aq);
+ }
+#endif
+ switch(aq->aq_state) {
+
+ case ASR_STATE_INIT:
+ if (aq->aq_family != AF_INET &&
+ aq->aq_family != AF_INET6 &&
+ aq->aq_family != AF_UNSPEC) {
+ ar->ar_err = EASR_FAMILY;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+ aq->aq_count = 0;
+ aq->aq_dom_idx = 0;
+ /* check if we need to try it as an absolute name first */
+ if (asr_ndots(aq->aq_host) >= aq->aq_ctx->ac_ndots)
+ aq->aq_dom_idx = -1;
+ aq->aq_state = ASR_STATE_SEARCH_DOMAIN;
+ break;
+
+ case ASR_STATE_NEXT_DOMAIN:
+ /* no domain search for fully qualified names */
+ if (asr_is_fqdn(aq->aq_host)) {
+ ar->ar_err = EASR_NOTFOUND;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+ aq->aq_dom_idx += 1;
+ aq->aq_state = ASR_STATE_SEARCH_DOMAIN;
+ break;
+
+ case ASR_STATE_SEARCH_DOMAIN:
+ if (aq->aq_dom_idx >= aq->aq_ctx->ac_domcount) {
+ ar->ar_err = EASR_NOTFOUND;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+ if (aq->aq_fqdn)
+ free(aq->aq_fqdn);
+
+ if (aq->aq_dom_idx == -1) /* try as absolute first */
+ aq->aq_fqdn = asr_make_fqdn(aq->aq_host, NULL);
+ else
+ aq->aq_fqdn = asr_make_fqdn(aq->aq_host,
+ aq->aq_ctx->ac_dom[aq->aq_dom_idx]);
+
+ if (aq->aq_fqdn == NULL) {
+ ar->ar_err = EASR_MEMORY;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+ aq->aq_db_idx = 0;
+ aq->aq_family_idx = 0;
+ aq->aq_state = ASR_STATE_LOOKUP_FAMILY;
+ break;
+
+ case ASR_STATE_NEXT_FAMILY:
+ aq->aq_family_idx += 1;
+ if ((aq->aq_family != AF_UNSPEC) || (AQ_FAMILY(aq) == -1)) {
+ /* The family was specified, or we have
+ * tried all families with this DB
+ */
+ if (aq->aq_count) {
+ ar->ar_count = aq->aq_count;
+ ar->ar_err = ASR_OK;
+ aq->aq_state = ASR_STATE_HALT;
+ } else
+ aq->aq_state = ASR_STATE_NEXT_DB;
+ break;
+ }
+ aq->aq_state = ASR_STATE_LOOKUP_FAMILY;
+ break;
+
+ case ASR_STATE_LOOKUP_FAMILY:
+ aq->aq_state = ASR_STATE_QUERY_DB;
+ break;
+
+ case ASR_STATE_NEXT_DB:
+ aq->aq_db_idx += 1;
+ aq->aq_family_idx = 0;
+ aq->aq_state = ASR_STATE_QUERY_DB;
+ break;
+
+ case ASR_STATE_QUERY_DB:
+ if (aq->aq_db_idx >= aq->aq_ctx->ac_dbcount) {
+ aq->aq_state = ASR_STATE_NEXT_DOMAIN;
+ break;
+ }
+
+ switch(AQ_DB(aq)->ad_type) {
+ case ASR_DB_DNS:
+ family = aq->aq_family;
+ if (family == AF_UNSPEC)
+ family = AQ_FAMILY(aq);
+ if (family == AF_INET)
+ aq->aq_query.q_type = T_A;
+ else if (family == AF_INET6)
+ aq->aq_query.q_type = T_AAAA;
+ else
+ errx(1, "bad family: %i", family);
+ aq->aq_query.q_class = C_IN;
+ aq->aq_flags = 0;
+ aq->aq_ns_cycles = 0;
+ aq->aq_ns_idx = 0;
+ aq->aq_state = ASR_STATE_QUERY_NS;
+ break;
+ case ASR_DB_FILE:
+ aq->aq_state = ASR_STATE_QUERY_FILE;
+ break;
+ default:
+ aq->aq_state = ASR_STATE_NEXT_DB;
+ }
+ break;
+
+ case ASR_STATE_NEXT_NS:
+ /* close the current fd if any */
+ if (aq->aq_fd != -1) {
+ close(aq->aq_fd);
+ aq->aq_fd = -1;
+ }
+
+ aq->aq_ns_idx += 1;
+ if (aq->aq_ns_idx >= AQ_DB(aq)->ad_count) {
+ aq->aq_ns_idx = 0;
+ aq->aq_ns_cycles++;
+ }
+ if (aq->aq_ns_cycles >= AQ_DB(aq)->ad_retries) {
+ aq->aq_state = ASR_STATE_NEXT_DB;
+ break;
+ }
+ aq->aq_state = ASR_STATE_QUERY_NS;
+ break;
+
+ case ASR_STATE_QUERY_NS:
+ if (aq->aq_ns_idx >= AQ_DB(aq)->ad_count) {
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+ }
+ switch (asr_setup_packet(aq)) {
+ case -2:
+ ar->ar_err = EASR_MEMORY;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ case -1:
+ ar->ar_err = EASR_NAME;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ default:
+ break;
+ }
+ if (aq->aq_ctx->ac_forcetcp)
+ aq->aq_state = ASR_STATE_TCP_WRITE;
+ else
+ aq->aq_state = ASR_STATE_UDP_SEND;
+ break;
+
+ case ASR_STATE_UDP_SEND:
+ if (asr_udp_send(aq) == 0) {
+ aq->aq_state = ASR_STATE_UDP_RECV;
+ ar->ar_fd = aq->aq_fd;
+ ar->ar_timeout = aq->aq_timeout;
+ return (ASR_NEED_READ);
+ }
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+
+ case ASR_STATE_UDP_RECV:
+ switch (asr_udp_recv(aq)) {
+ case -2: /* timeout */
+ case -1: /* fail */
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+ case 0: /* done */
+ aq->aq_state = ASR_STATE_PACKET;
+ break;
+ case 1: /* truncated */
+ close(aq->aq_fd);
+ aq->aq_fd = -1;
+ aq->aq_state = ASR_STATE_TCP_WRITE;
+ break;
+ }
+ break;
+
+ case ASR_STATE_TCP_WRITE:
+ switch (asr_tcp_write(aq)) {
+ case -2: /* timeout */
+ case -1: /* fail */
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+ case 0:
+ aq->aq_state = ASR_STATE_TCP_READ;
+ ar->ar_fd = aq->aq_fd;
+ ar->ar_timeout = aq->aq_timeout;
+ return (ASR_NEED_READ);
+ case 1:
+ ar->ar_fd = aq->aq_fd;
+ ar->ar_timeout = aq->aq_timeout;
+ return (ASR_NEED_WRITE);
+ }
+ break;
+
+ case ASR_STATE_TCP_READ:
+ switch (asr_tcp_read(aq)) {
+ case -3:
+ aq->aq_state = ASR_STATE_HALT;
+ ar->ar_err = EASR_MEMORY;
+ break;
+ case -2: /* timeout */
+ case -1: /* fail */
+ aq->aq_state = ASR_STATE_NEXT_NS;
+ break;
+ case 0:
+ aq->aq_state = ASR_STATE_PACKET;
+ break;
+ case 1:
+ ar->ar_fd = aq->aq_fd;
+ ar->ar_timeout = aq->aq_timeout;
+ return (ASR_NEED_READ);
+ }
+ break;
+
+ case ASR_STATE_PACKET:
+ packed_init(&aq->aq_packed, aq->aq_buf, aq->aq_buflen);
+ unpack_header(&aq->aq_packed, &h);
+ aq->aq_nanswer = h.ancount;
+ for(; h.qdcount; h.qdcount--)
+ unpack_query(&aq->aq_packed, &q);
+ aq->aq_state = ASR_STATE_READ_RR;
+ break;
+
+ case ASR_STATE_READ_RR:
+ if (aq->aq_nanswer == 0) {
+ free(aq->aq_buf);
+ aq->aq_buf = NULL;
+ /* done with this NS, try with next family */
+ aq->aq_state = ASR_STATE_NEXT_FAMILY;
+ break;
+ }
+ aq->aq_nanswer -= 1;
+ unpack_rr(&aq->aq_packed, &rr);
+ if (rr.rr_type == aq->aq_query.q_type &&
+ rr.rr_class == aq->aq_query.q_class) {
+ aq->aq_count += 1;
+ ar->ar_count = aq->aq_count;
+ sockaddr_from_rr(&ar->ar_sa.sa, &rr);
+ ar->ar_cname = NULL; /* XXX */
+ return (ASR_YIELD);
+ }
+ break;
+
+ case ASR_STATE_QUERY_FILE:
+ aq->aq_file = fopen(AQ_DB(aq)->ad_path, "r");
+ if (aq->aq_file == NULL)
+ aq->aq_state = ASR_STATE_NEXT_DB;
+ else
+ aq->aq_state = ASR_STATE_READ_FILE;
+ break;
+
+ case ASR_STATE_READ_FILE:
+ n = asr_parse_namedb_line(aq->aq_file, tok, ntok);
+ if (n == -1) {
+ fclose(aq->aq_file);
+ aq->aq_file = NULL;
+ /* XXX as an optimization, the file could be parsed only once */
+ aq->aq_state = ASR_STATE_NEXT_FAMILY;
+ break;
+ }
+
+ for (i = 1; i < n; i++) {
+ /* for the first round, try the host as-is */
+ /* XXX not nice */
+ if (aq->aq_dom_idx <= 0 && !strcmp(aq->aq_host, tok[i])) {
+ } else if (asr_cmp_fqdn_name(aq->aq_fqdn, tok[i]) == -1)
+ continue;
+ family = aq->aq_family;
+ if (family == AF_UNSPEC)
+ family = AQ_FAMILY(aq);
+ if (sockaddr_from_str(&ar->ar_sa.sa, family, tok[0]) == -1)
+ continue;
+
+ aq->aq_count += 1;
+ ar->ar_count = aq->aq_count;
+ ar->ar_cname = strdup(tok[1]);
+ return (ASR_YIELD);
+ }
+ break;
+
+ case ASR_STATE_HALT:
+ ar->ar_count = aq->aq_count;
+ ar->ar_errstr = asr_error(ar->ar_err);
+ return (ASR_DONE);
+
+ default:
+ errx(1, "asr_run_host: unknown state");
+ }}
+}
+
+
+
+/*
+ * for asr_query_addrinfo
+ */
+
+struct asr_query *
+asr_query_addrinfo(struct asr *asr,
+ const char *hostname,
+ const char *servname,
+ const struct addrinfo *hints)
+{
+ struct asr_query *aq;
+
+ asr_check_reload(asr);
+
+ if ((aq = asr_ctx_query(asr->a_ctx, ASR_QUERY_ADDRINFO)) == NULL)
+ return (NULL);
+
+ if (hostname && (aq->aq_hostname = strdup(hostname)) == NULL)
+ goto abort;
+ if (servname && (aq->aq_servname = strdup(servname)) == NULL)
+ goto abort;
+ if (hints)
+ memmove(&aq->aq_hints, hints, sizeof *hints);
+ else {
+ memset(&aq->aq_hints, 0, sizeof aq->aq_hints);
+ aq->aq_hints.ai_family = PF_UNSPEC;
+ }
+
+ return (aq);
+ abort:
+ asr_query_free(aq);
+ return (NULL);
+}
+
+int
+asr_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 htons(port);
+ if (errno == ERANGE)
+ return (-3); /* invalid */
+ if (numonly)
+ return (-3);
+
+ memset(&sed, 0, sizeof(sed));
+ r = getservbyname_r(servname, proto, &se, &sed);
+ port = se.s_port;
+ endservent_r(&sed);
+
+ if (r == -1)
+ return (-2); /* not found */
+
+ return (port);
+}
+
+int
+asr_add_sockaddr2(struct asr_query *aq,
+ struct sockaddr *sa,
+ int socktype,
+ int protocol)
+{
+ struct addrinfo *ai;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ const char *proto;
+ int port;
+
+ switch (protocol) {
+ case IPPROTO_TCP:
+ proto = "tcp";
+ break;
+ case IPPROTO_UDP:
+ proto = "udp";
+ break;
+ default:
+ proto = NULL;
+ }
+
+ port = -1;
+ if (proto) {
+ port = asr_get_port(aq->aq_servname, proto,
+ aq->aq_hints.ai_flags & AI_NUMERICSERV);
+ if (port < 0)
+ return (port);
+ }
+
+ ai = calloc(1, sizeof *ai + sa->sa_len);
+ if (ai == NULL)
+ return (-1); /* no mem */
+ ai->ai_family = sa->sa_family;
+ ai->ai_socktype = socktype;
+ ai->ai_protocol = protocol;
+ ai->ai_addrlen = sa->sa_len;
+ ai->ai_addr = (void*)(ai + 1);
+ memmove(ai->ai_addr, sa, sa->sa_len);
+
+ if (port != -1) {
+ switch(ai->ai_family) {
+ case PF_INET:
+ sin = (struct sockaddr_in*)ai->ai_addr;
+ sin->sin_port = port;
+ break;
+ case PF_INET6:
+ sin6 = (struct sockaddr_in6*)ai->ai_addr;
+ sin6->sin6_port = port;
+ break;
+ }
+ }
+
+ if (aq->aq_aifirst == NULL)
+ aq->aq_aifirst = ai;
+ if (aq->aq_ailast)
+ aq->aq_ailast->ai_next = ai;
+ aq->aq_ailast = ai;
+
+ aq->aq_count += 1;
+
+ return (0);
+}
+
+struct match {
+ int family;
+ int socktype;
+ int protocol;
+};
+
+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))
+
+int
+asr_add_sockaddr(struct asr_query *aq, struct sockaddr *sa)
+{
+ int i, e;
+
+ for(i = 0; matches[i].family != -1; i++) {
+ if (matches[i].family != sa->sa_family ||
+ !MATCH_SOCKTYPE(aq->aq_hints.ai_socktype, i) ||
+ !MATCH_PROTO(aq->aq_hints.ai_protocol, i))
+ continue;
+ e = asr_add_sockaddr2(aq, sa, matches[i].socktype, matches[i].protocol);
+ switch(e) {
+ case -3:
+ return (EAI_NONAME);
+ case -2:
+ /* Only report bad service if the protocol was specified */
+ if (aq->aq_hints.ai_protocol == 0)
+ break;
+ return (EAI_SERVICE);
+ case -1:
+ return (EAI_MEMORY);
+ }
+ }
+
+ return (0);
+}
+
+int
+asr_iter_family(struct asr_query *aq, int first)
+{
+ if (first) {
+ aq->aq_family_idx = 0;
+ if (aq->aq_hints.ai_family != PF_UNSPEC)
+ return aq->aq_hints.ai_family;
+ return AQ_FAMILY(aq);
+ }
+
+ if (aq->aq_hints.ai_family != PF_UNSPEC)
+ return (-1);
+
+ aq->aq_family_idx++;
+
+ return AQ_FAMILY(aq);
+}
+
+int
+asr_run_addrinfo(struct asr_query *aq, struct asr_result *ar)
+{
+ const char *str;
+ struct addrinfo *ai;
+ int i, family, r;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sain;
+ struct sockaddr_in6 sain6;
+ } sa;
+
+ for(;;) { /* block not indented on purpose */
+#ifdef ASR_DEBUG
+ if (asr_debug) {
+ printf(" ");
+ asr_dump_query(aq);
+ }
+#endif
+ switch(aq->aq_state) {
+
+ case ASR_STATE_INIT:
+ aq->aq_count = 0;
+ aq->aq_state = ASR_STATE_HALT;
+ ar->ar_err = 0;
+
+ if (aq->aq_hostname == NULL &&
+ aq->aq_servname == NULL) {
+ ar->ar_err = EAI_BADHINTS;
+ break;
+ }
+
+ ai = &aq->aq_hints;
+
+ if (ai->ai_addrlen ||
+ ai->ai_canonname ||
+ ai->ai_addr ||
+ ai->ai_next) {
+ ar->ar_err = EAI_BADHINTS;
+ break;
+ }
+
+ if (ai->ai_flags & ~AI_MASK) {
+ ar->ar_err = EAI_BADHINTS;
+ break;
+ }
+
+ if (ai->ai_family != PF_UNSPEC &&
+ ai->ai_family != PF_INET &&
+ ai->ai_family != PF_INET6) {
+ ar->ar_err = EAI_FAMILY;
+ break;
+ }
+
+ if (ai->ai_socktype &&
+ ai->ai_socktype != SOCK_DGRAM &&
+ ai->ai_socktype != SOCK_STREAM &&
+ ai->ai_socktype != SOCK_RAW) {
+ ar->ar_err = EAI_SOCKTYPE;
+ break;
+ }
+
+ if (ai->ai_protocol &&
+ ai->ai_protocol != IPPROTO_UDP &&
+ ai->ai_protocol != IPPROTO_TCP) {
+ ar->ar_err = EAI_PROTOCOL;
+ break;
+ }
+
+ if (ai->ai_socktype == SOCK_RAW &&
+ aq->aq_servname != NULL) {
+ ar->ar_err = 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_err = EAI_BADHINTS;
+ break;
+ }
+
+ if (aq->aq_hostname == NULL) {
+ for(family = asr_iter_family(aq, 1);
+ family != -1;
+ family = asr_iter_family(aq, 0)) {
+ 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";
+ /* can't fail */
+ sockaddr_from_str(&sa.sa, family, str);
+ if ((r = asr_add_sockaddr(aq, &sa.sa))) {
+ ar->ar_err = r;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+ }
+ if (ar->ar_err == 0 && aq->aq_count == 0)
+ ar->ar_err = EAI_NODATA;
+ break;
+ }
+
+ /* try numeric addresses */
+ for(family = asr_iter_family(aq, 1);
+ family != -1;
+ family = asr_iter_family(aq, 0)) {
+
+ if (sockaddr_from_str(&sa.sa, family,
+ aq->aq_hostname) == -1)
+ continue;
+
+ if ((r = asr_add_sockaddr(aq, &sa.sa))) {
+ ar->ar_err = r;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+ if (ar->ar_err || aq->aq_count)
+ break;
+
+ if (ai->ai_flags & AI_NUMERICHOST) {
+ ar->ar_err = EAI_FAIL;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+
+ /* subquery for hostname */
+ if ((aq->aq_subq = asr_ctx_query_host(aq->aq_ctx,
+ aq->aq_hostname,
+ ai->ai_family)) == NULL) {
+ ar->ar_err = EAI_MEMORY;
+ aq->aq_state = ASR_STATE_HALT;
+ }
+
+ aq->aq_state = ASR_STATE_SUBQUERY;
+ break;
+
+ case ASR_STATE_SUBQUERY:
+ switch ((r = asr_run(aq->aq_subq, ar))) {
+ case ASR_NEED_READ:
+ case ASR_NEED_WRITE:
+ return (r);
+ case ASR_YIELD:
+ if ((r = asr_add_sockaddr(aq, &ar->ar_sa.sa))) {
+ ar->ar_err = r;
+ aq->aq_state = ASR_STATE_HALT;
+ }
+ free(ar->ar_cname);
+ break;
+ case ASR_DONE:
+ aq->aq_subq = NULL;
+ if (ar->ar_count == 0)
+ ar->ar_err = EAI_NODATA;
+ else if (aq->aq_count == 0)
+ ar->ar_err = EAI_NONAME;
+ else
+ ar->ar_err = 0;
+ aq->aq_state = ASR_STATE_HALT;
+ break;
+ }
+ break;
+
+ case ASR_STATE_HALT:
+ if (ar->ar_err == 0) {
+ ar->ar_errstr = NULL;
+ ar->ar_count = aq->aq_count;
+ ar->ar_ai = aq->aq_aifirst;
+ aq->aq_aifirst = NULL;
+ } else {
+ ar->ar_ai = NULL;
+ ar->ar_errstr = gai_strerror(ar->ar_err);
+ }
+ return (ASR_DONE);
+
+ default:
+ errx(1, "asr_run_addrinfo: unknown state");
+ }}
+}