Add an asynchronous resolver library.
authorMichael Poole <mdpoole@troilus.org>
Mon, 19 Mar 2007 03:05:55 +0000 (23:05 -0400)
committerMichael Poole <mdpoole@troilus.org>
Mon, 19 Mar 2007 03:05:55 +0000 (23:05 -0400)
src/Makefile.am: Add sar.* to the build.
src/main.c: Call sar_init().
src/sar.*: The srvx asynchronous resolver.
src/mod-blacklist.c: Use it to add DNSBL support.
srvx.conf.eaxmple: Document the DNSBL support and sar options.

src/Makefile.am
src/main.c
src/mod-blacklist.c
src/sar.c [new file with mode: 0644]
src/sar.h [new file with mode: 0644]
srvx.conf.example

index 73860045e444402534ecad0d04f54c8173f9e362..f24e00000aa854afa789d9d9378f792df8b93bc2 100644 (file)
@@ -81,6 +81,7 @@ srvx_SOURCES = \
        policer.c policer.h \
        proto.h \
        recdb.c recdb.h \
+       sar.c sar.h \
        saxdb.c saxdb.h \
        timeq.c timeq.h \
        tools.c
index e5556a8eec7fa4f3785353e5e9f23aae9b35f595..6966853e801bbdaec6ebbb6a185c804b3ce85480 100644 (file)
@@ -27,6 +27,7 @@
 #include "saxdb.h"
 #include "mail.h"
 #include "timeq.h"
+#include "sar.h"
 
 #include "chanserv.h"
 #include "global.h"
@@ -286,6 +287,7 @@ int main(int argc, char *argv[])
     init_parse();
     modcmd_init();
     saxdb_init();
+    sar_init();
     gline_init();
     mail_init();
     helpfile_init();
index f90f59617ee016683a7ecdd560ee14e4e830abde..7ae95260cced1326a9eea7244f3d13d807082508 100644 (file)
 #include "gline.h"
 #include "modcmd.h"
 #include "proto.h"
+#include "sar.h"
 
 const char *blacklist_module_deps[] = { NULL };
 
+struct dnsbl_zone {
+    struct string_list reasons;
+    const char *description;
+    const char *reason;
+    unsigned int duration;
+    unsigned int mask;
+    char zone[1];
+};
+
+struct dnsbl_data {
+    char client_ip[IRC_NTOP_MAX_SIZE];
+    char zone_name[1];
+};
+
 static struct log_type *bl_log;
+static dict_t blacklist_zones; /* contains struct dnsbl_zone */
 static dict_t blacklist_hosts; /* maps IPs or hostnames to reasons from blacklist_reasons */
 static dict_t blacklist_reasons; /* maps strings to themselves (poor man's data sharing) */
 
@@ -33,13 +49,119 @@ static struct {
     unsigned long gline_duration;
 } conf;
 
+static void
+do_expandos(char *output, unsigned int out_len, const char *input, ...)
+{
+    va_list args;
+    const char *key;
+    const char *datum;
+    char *found;
+    unsigned int klen;
+    unsigned int dlen;
+    unsigned int rlen;
+
+    safestrncpy(output, input, out_len);
+    va_start(args, input);
+    while ((key = va_arg(args, const char*)) != NULL) {
+        datum = va_arg(args, const char *);
+        klen = strlen(key);
+        dlen = strlen(datum);
+        for (found = output; (found = strstr(output, key)) != NULL; found += dlen) {
+            rlen = strlen(found + klen);
+            if ((dlen > klen) && (found + dlen + rlen - output > out_len))
+                rlen = output + out_len - found - dlen;
+            memmove(found + dlen, found + klen, rlen);
+            memcpy(found, datum, dlen + 1);
+        }
+    }
+    va_end(args);
+}
+
+static void
+dnsbl_hit(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size)
+{
+    struct dnsbl_data *data;
+    struct dnsbl_zone *zone;
+    const char *message;
+    char *txt;
+    unsigned int mask;
+    unsigned int pos;
+    unsigned int len;
+    unsigned int ii;
+    char reason[MAXLEN];
+    char target[IRC_NTOP_MAX_SIZE + 2];
+
+    /* Get the DNSBL zone (to make sure it has not disappeared in a rehash). */
+    data = (struct dnsbl_data*)(req + 1);
+    zone = dict_find(blacklist_zones, data->zone_name, NULL);
+    if (!zone)
+        return;
+
+    /* Scan the results. */
+    for (mask = 0, ii = 0, txt = NULL; ii < hdr->ancount; ++ii) {
+        pos = rr[ii].rd_start;
+        switch (rr[ii].type) {
+        case REQ_TYPE_A:
+            if (rr[ii].rdlength != 4)
+                break;
+            if (pos + 3 < raw_size)
+                mask |= (1 << raw[pos + 3]);
+            break;
+        case REQ_TYPE_TXT:
+            len = raw[pos];
+            txt = malloc(len + 1);
+            memcpy(txt, raw + pos + 1, len);
+            txt[len] = '\0';
+            break;
+        }
+    }
+
+    /* Do we care about one of the masks we found? */
+    if (mask & zone->mask) {
+        /* See if a per-result message was provided. */
+        for (ii = 0, message = NULL; mask && (ii < zone->reasons.used); ++ii, mask >>= 1) {
+            if (0 == (mask & 1))
+                continue;
+            if (NULL != (message = zone->reasons.list[ii]))
+                break;
+        }
+
+        /* If not, use a standard fallback. */
+        if (message == NULL) {
+            message = zone->reason;
+            if (message == NULL)
+                message = "client is blacklisted";
+        }
+
+        /* Expand elements of the message as necessary. */
+        do_expandos(reason, sizeof(reason), message, "%txt%", (txt ? txt : "(no-txt)"), "%ip%", data->client_ip, NULL);
+
+        /* Now generate the G-line. */
+        target[0] = '*';
+        target[1] = '@';
+        strcpy(target + 2, data->client_ip);
+        gline_add(self->name, target, zone->duration, reason, now, now, 1);
+    }
+    free(txt);
+}
+
 static int
 blacklist_check_user(struct userNode *user)
 {
+    static const char *hexdigits = "0123456789abcdef";
+    dict_iterator_t it;
     const char *reason;
     const char *host;
+    unsigned int dnsbl_len;
+    unsigned int ii;
     char ip[IRC_NTOP_MAX_SIZE];
+    char dnsbl_target[128];
+
+    /* Users with bogus IPs are probably service bots. */
+    if (!irc_in_addr_is_valid(user->ip))
+        return 0;
 
+    /* Check local file-based blacklist. */
     irc_ntop(ip, sizeof(ip), &user->ip);
     reason = dict_find(blacklist_hosts, host = ip, NULL);
     if (reason == NULL) {
@@ -53,6 +175,37 @@ blacklist_check_user(struct userNode *user)
         strcpy(target + 2, host);
         gline_add(self->name, target, conf.gline_duration, reason, now, now, 1);
     }
+
+    /* Figure out the base part of a DNS blacklist hostname. */
+    if (irc_in_addr_is_ipv4(user->ip)) {
+        dnsbl_len = snprintf(dnsbl_target, sizeof(dnsbl_target), "%d.%d.%d.%d.", user->ip.in6_8[15], user->ip.in6_8[14], user->ip.in6_8[13], user->ip.in6_8[12]);
+    } else if (irc_in_addr_is_ipv6(user->ip)) {
+        for (ii = 0; ii < 16; ++ii) {
+            dnsbl_target[ii * 4 + 0] = hexdigits[user->ip.in6_8[15 - ii] & 15];
+            dnsbl_target[ii * 4 + 1] = '.';
+            dnsbl_target[ii * 4 + 2] = hexdigits[user->ip.in6_8[15 - ii] >> 4];
+            dnsbl_target[ii * 4 + 3] = '.';
+        }
+        dnsbl_len = 48;
+    } else {
+        return 0;
+    }
+
+    /* Start a lookup for the appropriate hostname in each DNSBL. */
+    for (it = dict_first(blacklist_zones); it; it = iter_next(it)) {
+        struct dnsbl_data *data;
+        struct sar_request *req;
+        const char *zone;
+
+        zone = iter_key(it);
+        safestrncpy(dnsbl_target + dnsbl_len, zone, sizeof(dnsbl_target) - dnsbl_len);
+        req = sar_request_simple(sizeof(*data) + strlen(zone), dnsbl_hit, NULL, dnsbl_target, REQ_QTYPE_ALL, NULL);
+        if (req) {
+            data = (struct dnsbl_data*)(req + 1);
+            strcpy(data->client_ip, ip);
+            strcpy(data->zone_name, zone);            
+        }
+    }
     return 0;
 }
 
@@ -106,20 +259,34 @@ blacklist_load_file(const char *filename, const char *default_reason)
     fclose(file);
 }
 
+static void
+dnsbl_zone_free(void *pointer)
+{
+    struct dnsbl_zone *zone;
+    zone = pointer;
+    free(zone->reasons.list);
+    free(zone);
+}
+
 static void
 blacklist_conf_read(void)
 {
     dict_t node;
+    dict_t subnode;
     const char *str1;
     const char *str2;
 
+    dict_delete(blacklist_zones);
+    blacklist_zones = dict_new();
+    dict_set_free_data(blacklist_zones, free);
+
     dict_delete(blacklist_hosts);
     blacklist_hosts = dict_new();
     dict_set_free_keys(blacklist_hosts, free);
 
     dict_delete(blacklist_reasons);
     blacklist_reasons = dict_new();
-    dict_set_free_keys(blacklist_reasons, free);
+    dict_set_free_keys(blacklist_reasons, dnsbl_zone_free);
 
     node = conf_get_data("modules/blacklist", RECDB_OBJECT);
     if (node == NULL)
@@ -133,11 +300,60 @@ blacklist_conf_read(void)
     if (str1 == NULL)
         str1 = "1h";
     conf.gline_duration = ParseInterval(str1);
+
+    subnode = database_get_data(node, "dnsbl", RECDB_OBJECT);
+    if (subnode) {
+        static const char *reason_prefix = "reason_";
+        static const unsigned int max_id = 255;
+        struct dnsbl_zone *zone;
+        dict_iterator_t it;
+        dict_iterator_t it2;
+        dict_t dnsbl;
+        unsigned int id;
+
+        for (it = dict_first(subnode); it; it = iter_next(it)) {
+            dnsbl = GET_RECORD_OBJECT((struct record_data*)iter_data(it));
+            if (!dnsbl)
+                continue;
+
+            zone = malloc(sizeof(*zone) + strlen(iter_key(it)));
+            strcpy(zone->zone, iter_key(it));
+            zone->description = database_get_data(dnsbl, "description", RECDB_QSTRING);
+            zone->reason = database_get_data(dnsbl, "reason", RECDB_QSTRING);
+            str1 = database_get_data(dnsbl, "duration", RECDB_QSTRING);
+            zone->duration = str1 ? ParseInterval(str1) : 3600;
+            str1 = database_get_data(dnsbl, "mask", RECDB_QSTRING);
+            zone->mask = str1 ? strtoul(str1, NULL, 0) : ~0u;
+            zone->reasons.used = 0;
+            zone->reasons.size = 0;
+            zone->reasons.list = NULL;
+            dict_insert(blacklist_zones, zone->zone, zone);
+
+            for (it2 = dict_first(dnsbl); it2; it2 = iter_next(it2)) {
+                str1 = GET_RECORD_QSTRING((struct record_data*)(iter_data(it2)));
+                if (!str1 || memcmp(iter_key(it2), reason_prefix, strlen(reason_prefix)))
+                    continue;
+                id = strtoul(iter_key(it2) + strlen(reason_prefix), NULL, 0);
+                if (id > max_id) {
+                    log_module(bl_log, LOG_ERROR, "Invalid code for DNSBL %s %s -- only %d responses supported.", iter_key(it), iter_key(it2), max_id);
+                    continue;
+                }
+                if (zone->reasons.size < id + 1) {
+                    zone->reasons.size = id + 1;
+                    zone->reasons.list = realloc(zone->reasons.list, zone->reasons.size * sizeof(zone->reasons.list[0]));
+                }
+                zone->reasons.list[id] = (char*)str1;
+                if (zone->reasons.used < id + 1)
+                    zone->reasons.used = id + 1;
+            }
+        }
+    }
 }
 
 static void
 blacklist_cleanup(void)
 {
+    dict_delete(blacklist_zones);
     dict_delete(blacklist_hosts);
     dict_delete(blacklist_reasons);
 }
@@ -145,7 +361,7 @@ blacklist_cleanup(void)
 int
 blacklist_init(void)
 {
-    bl_log = log_register_type("Blacklist", "file:blacklist.log");
+    bl_log = log_register_type("blacklist", "file:blacklist.log");
     conf_register_reload(blacklist_conf_read);
     reg_new_user_func(blacklist_check_user);
     reg_exit_func(blacklist_cleanup);
diff --git a/src/sar.c b/src/sar.c
new file mode 100644 (file)
index 0000000..545c890
--- /dev/null
+++ b/src/sar.c
@@ -0,0 +1,2043 @@
+/* sar.h - srvx asynchronous resolver
+ * Copyright 2005, 2007 Michael Poole <mdpoole@troilus.org>
+ *
+ * This file is part of srvx.
+ *
+ * srvx is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with srvx; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+
+#include "sar.h"
+#include "conf.h"
+#include "ioset.h"
+#include "log.h"
+#include "timeq.h"
+
+static const char hexdigits[] = "0123456789abcdef";
+
+struct dns_rr;
+struct sar_getaddr_state;
+struct sar_getname_state;
+
+struct sar_family_helper {
+    const char *localhost_addr;
+    const char *unspec_addr;
+    unsigned int socklen;
+    unsigned int family;
+
+    unsigned int (*ntop)(char *output, unsigned int out_size, const struct sockaddr *sa, unsigned int socklen);
+    unsigned int (*pton)(struct sockaddr *sa, unsigned int socklen, unsigned int *bits, const char *input);
+    int (*get_port)(const struct sockaddr *sa, unsigned int socklen);
+    int (*set_port)(struct sockaddr *sa, unsigned int socklen, unsigned short port);
+    unsigned int (*build_addr_request)(struct sar_request *req, const char *node, const char *srv_node, unsigned int flags);
+    void (*build_ptr_name)(struct sar_getname_state *state, const struct sockaddr *sa, unsigned int socklen);
+    int (*decode_addr)(struct sar_getaddr_state *state, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size);
+
+    struct sar_family_helper *next;
+};
+
+#define MAX_FAMILY AF_INET
+static struct sar_family_helper sar_ipv4_helper;
+
+#if defined(AF_INET6)
+# if AF_INET6 > MAX_FAMILY
+#  undef MAX_FAMILY
+#  define MAX_FAMILY AF_INET6
+# endif
+static struct sar_family_helper sar_ipv6_helper;
+#endif
+
+static struct sar_family_helper *sar_helpers[MAX_FAMILY+1];
+static struct sar_family_helper *sar_first_helper;
+
+unsigned int
+sar_ntop(char *output, unsigned int out_size, const struct sockaddr *sa, unsigned int socklen)
+{
+    unsigned int pos;
+    assert(output != NULL);
+    assert(sa != NULL);
+    assert(out_size > 0);
+
+    if (sa->sa_family <= MAX_FAMILY && sar_helpers[sa->sa_family]) {
+        pos = sar_helpers[sa->sa_family]->ntop(output, out_size, sa, socklen);
+        if (pos)
+            return pos;
+    }
+    *output = '\0';
+    return 0;
+}
+
+unsigned int
+sar_pton(struct sockaddr *sa, unsigned int socklen, unsigned int *bits, const char *input)
+{
+    struct sar_family_helper *helper;
+    unsigned int len;
+
+    assert(sa != NULL);
+    assert(input != NULL);
+
+    memset(sa, 0, socklen);
+    if (bits)
+        *bits = ~0;
+    for (helper = sar_first_helper; helper; helper = helper->next) {
+        if (socklen < helper->socklen)
+            continue;
+        len = helper->pton(sa, socklen, bits, input);
+        if (len) {
+            sa->sa_family = helper->family;
+            return len;
+        }
+    }
+    return 0; /* parse failed */
+}
+
+int
+sar_get_port(const struct sockaddr *sa, unsigned int socklen)
+{
+    if (sa->sa_family <= MAX_FAMILY
+        && sar_helpers[sa->sa_family]
+        && socklen >= sar_helpers[sa->sa_family]->socklen)
+        return sar_helpers[sa->sa_family]->get_port(sa, socklen);
+    else return -1;
+}
+
+int
+sar_set_port(struct sockaddr *sa, unsigned int socklen, unsigned short port)
+{
+    if (sa->sa_family <= MAX_FAMILY
+        && sar_helpers[sa->sa_family]
+        && socklen >= sar_helpers[sa->sa_family]->socklen)
+        return sar_helpers[sa->sa_family]->set_port(sa, socklen, port);
+    else return 1;
+}
+
+const char *
+sar_strerror(enum sar_errcode errcode)
+{
+    switch (errcode) {
+    case SAI_SUCCESS: return "Resolution succeeded.";
+    case SAI_FAMILY: return "The requested address family is not supported.";
+    case SAI_SOCKTYPE: return "The requested socket type is not supported.";
+    case SAI_BADFLAGS: return "Invalid flags value.";
+    case SAI_NONAME: return "Unknown name or service.";
+    case SAI_SERVICE: return "The service is unavailable for that socket type.";
+    case SAI_ADDRFAMILY: return "The host has no address in the requested family.";
+    case SAI_NODATA: return "The host has no addresses at all.";
+    case SAI_MEMORY: return "Unable to allocate memory.";
+    case SAI_FAIL: return "The nameserver indicated a permanent error.";
+    case SAI_AGAIN: return "The nameserver indicated a temporary error.";
+    case SAI_MISMATCH: return "Mismatch between reverse and forward resolution.";
+    case SAI_SYSTEM: return strerror(errno);
+    default: return "Unknown resolver error code.";
+    }
+}
+
+void
+sar_free(struct addrinfo *ai)
+{
+    struct addrinfo *next;
+    for (; ai; ai = next) {
+        next = ai->ai_next;
+        free(ai);
+    }
+}
+
+/** Global variables to support DNS name resolution. */
+static struct {
+    unsigned int sar_timeout;
+    unsigned int sar_retries;
+    unsigned int sar_ndots;
+    unsigned int sar_edns0;
+    char sar_localdomain[MAXLEN];
+    struct string_list *sar_search;
+    struct string_list *sar_nslist;
+    struct sockaddr_storage sar_bind_address;
+} conf;
+static struct log_type *sar_log;
+
+/* Except as otherwise noted, constants and formats are from RFC1035.
+ * This resolver is believed to implement the behaviors mandated (and
+ * in many cases those recommended) by these standards: RFC1035,
+ * RFC2671, RFC2782, RFC3596, RFC3597.
+ *
+ * Update queries (including RFC 2136) seems a likely candidate for
+ * future support.
+ * DNSSEC (including RFCs 2535, 3007, 3655, etc) is less likely until
+ * a good application is found.
+ * Caching (RFC 2308) and redirection (RFC 2672) are much less likely,
+ * since most users will have a separate local, caching, recursive
+ * nameserver.
+ * Other DNS extensions (at least through RFC 3755) are believed to be
+ * too rare or insufficiently useful to bother supporting.
+ *
+ * The following are useful Reasons For Concern:
+ * RFC1536, RFC1912, RFC2606, RFC3363, RFC3425, RFC3467
+ * http://www.iana.org/assignments/dns-parameters
+ * http://www.ietf.org/html.charters/dnsext-charter.html
+ */
+
+struct sar_nameserver {
+    char *name;
+    unsigned int valid;
+    unsigned int req_sent;
+    unsigned int resp_used;
+    unsigned int resp_ignored;
+    unsigned int resp_servfail;
+    unsigned int resp_fallback;
+    unsigned int resp_failures;
+    unsigned int resp_scrambled;
+    unsigned int ss_len;
+    struct sockaddr_storage ss;
+};
+
+/* EDNS0 uses 12 bit RCODEs, TSIG/TKEY use 16 bit RCODEs.
+ * Declare local RCODE failures here.*/
+enum {
+    RCODE_TIMED_OUT = 65536,
+    RCODE_QUERY_TOO_LONG,
+    RCODE_LABEL_TOO_LONG,
+    RCODE_SOCKET_FAILURE,
+    RCODE_DESTROYED,
+};
+
+#define DNS_NAME_LENGTH 256
+
+#define RES_SIZE_FLAGS 0xc0
+#define RES_SF_LABEL   0x00
+#define RES_SF_POINTER 0xc0
+
+static dict_t sar_requests;
+static dict_t sar_nameservers;
+static struct io_fd *sar_fd;
+static int sar_fd_fd;
+
+const char *
+sar_rcode_text(unsigned int rcode)
+{
+    switch (rcode) {
+    case RCODE_NO_ERROR: return "No error";
+    case RCODE_FORMAT_ERROR: return "Format error";
+    case RCODE_SERVER_FAILURE: return "Server failure";
+    case RCODE_NAME_ERROR: return "Name error";
+    case RCODE_NOT_IMPLEMENTED: return "Feature not implemented";
+    case RCODE_REFUSED: return "Query refused";
+    case RCODE_BAD_OPT_VERSION: return "Unsupported EDNS option version";
+    case RCODE_TIMED_OUT: return "Request timed out";
+    case RCODE_QUERY_TOO_LONG: return "Query too long";
+    case RCODE_LABEL_TOO_LONG: return "Label too long";
+    case RCODE_SOCKET_FAILURE: return "Resolver socket failure";
+    case RCODE_DESTROYED: return "Request unexpectedly destroyed";
+    default: return "Unknown rcode";
+    }
+}
+
+static void
+sar_request_fail(struct sar_request *req, unsigned int rcode)
+{
+    log_module(sar_log, LOG_DEBUG, "sar_request_fail({id=%d}, rcode=%d)", req->id, rcode);
+    req->expiry = 0;
+    if (req->cb_fail) {
+        req->cb_fail(req, rcode);
+        if (req->expiry)
+            return;
+    }
+    sar_request_abort(req);
+}
+
+static time_t next_sar_timeout;
+
+static void
+sar_timeout_cb(void *data)
+{
+    dict_iterator_t it;
+    dict_iterator_t next;
+    time_t next_timeout = INT_MAX;
+
+    for (it = dict_first(sar_requests); it; it = next) {
+        struct sar_request *req;
+
+        req = iter_data(it);
+        next = iter_next(it);
+        if (req->expiry > next_timeout)
+            continue;
+        else if (req->expiry > now)
+            next_timeout = req->expiry;
+        else if (req->retries >= conf.sar_retries)
+            sar_request_fail(req, RCODE_TIMED_OUT);
+        else
+            sar_request_send(req);
+    }
+    if (next_timeout < INT_MAX) {
+        next_sar_timeout = next_timeout;
+        timeq_add(next_timeout, sar_timeout_cb, data);
+    }
+}
+
+static void
+sar_check_timeout(time_t when)
+{
+    if (!next_sar_timeout || when < next_sar_timeout) {
+        timeq_del(0, sar_timeout_cb, NULL, TIMEQ_IGNORE_WHEN | TIMEQ_IGNORE_DATA);
+        timeq_add(when, sar_timeout_cb, NULL);
+        next_sar_timeout = when;
+    }
+}
+
+static void
+sar_request_cleanup(void *d)
+{
+    struct sar_request *req = d;
+    log_module(sar_log, LOG_DEBUG, "sar_request_cleanup({id=%d})", req->id);
+    free(req->body);
+    if (req->cb_fail)
+        req->cb_fail(req, RCODE_DESTROYED);
+    free(req);
+}
+
+static void
+sar_dns_init(const char *resolv_conf_path)
+{
+    struct string_list *ns_sv;
+    struct string_list *ds_sv;
+    FILE *resolv_conf;
+    dict_t node;
+    const char *str;
+
+    /* Initialize configuration defaults. */
+    conf.sar_localdomain[0] = '\0';
+    conf.sar_timeout = 3;
+    conf.sar_retries = 3;
+    conf.sar_ndots = 1;
+    conf.sar_edns0 = 0;
+    ns_sv = alloc_string_list(4);
+    ds_sv = alloc_string_list(4);
+
+    /* Scan resolver configuration file.  */
+    resolv_conf = fopen(resolv_conf_path, "r");
+    if (resolv_conf) {
+        char *arg, *opt;
+        unsigned int len;
+        char linebuf[LINE_MAX], ch;
+
+        while (fgets(linebuf, sizeof(linebuf), resolv_conf)) {
+            ch = linebuf[len = strcspn(linebuf, " \t\r\n")];
+            linebuf[len] = '\0';
+            arg = linebuf + len + 1;
+            if (!strcmp(linebuf, "nameserver")) {
+                while (ch == ' ') {
+                    ch = arg[len = strcspn(arg, " \t\r\n")];
+                    arg[len] = '\0';
+                    string_list_append(ns_sv, strdup(arg));
+                    arg += len + 1;
+                }
+            } else if (!strcmp(linebuf, "domain")) {
+                if (ch == ' ') {
+                    safestrncpy(conf.sar_localdomain, arg, sizeof(conf.sar_localdomain));
+                }
+            } else if (!strcmp(linebuf, "search")) {
+                while (ch == ' ') {
+                    ch = arg[len = strcspn(arg, " \t\r\n")];
+                    arg[len] = '\0';
+                    string_list_append(ds_sv, strdup(arg));
+                    arg += len + 1;
+                }
+            } else if (!strcmp(linebuf, "options")) {
+                while (ch == ' ') {
+                    ch = arg[len = strcspn(arg, " \t\r\n")];
+                    arg[len] = '\0';
+                    opt = strchr(arg, ':');
+                    if (opt) {
+                        *opt++ = '\0';
+                        if (!strcmp(arg, "timeout")) {
+                            conf.sar_timeout = atoi(opt);
+                        } else if (!strcmp(arg, "attempts")) {
+                            conf.sar_retries = atoi(opt);
+                        } else if (!strcmp(arg, "ndots")) {
+                            conf.sar_ndots = atoi(opt);
+                        } else if (!strcmp(arg, "edns0")) {
+                            conf.sar_edns0 = atoi(opt);
+                        }
+                    } else if (!strcmp(arg, "edns0")) {
+                        conf.sar_edns0 = 1440;
+                    }
+                    arg += len + 1;
+                }
+            }
+        }
+        fclose(resolv_conf);
+    } else {
+        /* This is apparently what BIND defaults to using. */
+        string_list_append(ns_sv, "127.0.0.1");
+    }
+
+    /* Set default search path if domain is set. */
+    if (conf.sar_localdomain[0] != '\0' && ds_sv->used == 0)
+        string_list_append(ds_sv, strdup(conf.sar_localdomain));
+
+    /* Check configuration entries that might override resolv.conf. */
+    node = conf_get_data("modules/sar", RECDB_OBJECT);
+    if (node) {
+        struct sockaddr *sa;
+        struct string_list *slist;
+
+        str = database_get_data(node, "timeout", RECDB_QSTRING);
+        if (str) conf.sar_timeout = ParseInterval(str);
+        str = database_get_data(node, "retries", RECDB_QSTRING);
+        if (str) conf.sar_retries = atoi(str);
+        str = database_get_data(node, "ndots", RECDB_QSTRING);
+        if (str) conf.sar_ndots = atoi(str);
+        str = database_get_data(node, "edns0", RECDB_QSTRING);
+        if (str) conf.sar_edns0 = enabled_string(str);
+        str = database_get_data(node, "domain", RECDB_QSTRING);
+        if (str) safestrncpy(conf.sar_localdomain, str, sizeof(conf.sar_localdomain));
+        slist = database_get_data(node, "search", RECDB_STRING_LIST);
+        if (slist) {
+            free_string_list(ds_sv);
+            ds_sv = string_list_copy(slist);
+        }
+        slist = database_get_data(node, "nameservers", RECDB_STRING_LIST);
+        if (slist) {
+            free_string_list(ns_sv);
+            ns_sv = string_list_copy(slist);
+        }
+        sa = (struct sockaddr*)&conf.sar_bind_address;
+        memset(sa, 0, sizeof(conf.sar_bind_address));
+        str = database_get_data(node, "bind_address", RECDB_QSTRING);
+        if (str) sar_pton(sa, sizeof(conf.sar_bind_address), NULL, str);
+        str = database_get_data(node, "bind_port", RECDB_QSTRING);
+        if (str != NULL) {
+            if (sa->sa_family == AF_INET) {
+                struct sockaddr_in *sin = (struct sockaddr_in*)sa;
+                sin->sin_port = ntohs(atoi(str));
+            }
+#if defined(AF_INET6)
+            else if (sa->sa_family == AF_INET6) {
+                struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)sa;
+                sin6->sin6_port = ntohs(atoi(str));
+            }
+#endif
+        }
+    }
+
+    /* Replace config lists with their new values. */
+    free_string_list(conf.sar_search);
+    conf.sar_search = ds_sv;
+    free_string_list(conf.sar_nslist);
+    conf.sar_nslist = ns_sv;
+}
+
+void
+sar_request_abort(struct sar_request *req)
+{
+    if (!req)
+        return;
+    assert(dict_find(sar_requests, req->id_text, NULL) == req);
+    log_module(sar_log, LOG_DEBUG, "sar_request_abort({id=%d})", req->id);
+    req->cb_ok = NULL;
+    req->cb_fail = NULL;
+    dict_remove(sar_requests, req->id_text);
+}
+
+static struct sar_nameserver *
+sar_our_server(const struct sockaddr_storage *ss, unsigned int ss_len)
+{
+    dict_iterator_t it;
+
+    for (it = dict_first(sar_nameservers); it; it = iter_next(it)) {
+        struct sar_nameserver *ns;
+
+        ns = iter_data(it);
+        if (ns->ss_len == ss_len && !memcmp(ss, &ns->ss, ss_len))
+            return ns;
+    }
+    return NULL;
+}
+
+char *
+sar_extract_name(const unsigned char *buf, unsigned int size, unsigned int *ppos)
+{
+    struct string_buffer cv;
+    unsigned int pos, jumped;
+
+    pos = *ppos;
+    jumped = 0;
+    cv.used = 0;
+    cv.size = 64;
+    cv.list = calloc(1, cv.size);
+    while (1) {
+        if (pos >= size)
+            goto fail;
+        if (!buf[pos]) {
+            if (!jumped)
+                *ppos = pos + 1;
+            if (cv.used)
+                cv.list[cv.used - 1] = '\0'; /* chop off terminating '.' */
+            else
+                string_buffer_append(&cv, '\0');
+            return cv.list;
+        }
+        switch (buf[pos] & RES_SIZE_FLAGS) {
+        case RES_SF_LABEL: {
+            unsigned int len = buf[pos];
+            if (pos + len + 1 >= size)
+                goto fail;
+            string_buffer_append_substring(&cv, (char*)buf + pos + 1, len);
+            string_buffer_append(&cv, '.');
+            pos += buf[pos] + 1;
+            break;
+        }
+        case RES_SF_POINTER:
+            if ((pos + 1 >= size) || (cv.used >= size))
+                goto fail;
+            if (!jumped)
+                *ppos = pos + 2;
+            pos = (buf[pos] & ~RES_SIZE_FLAGS) << 8 | buf[pos+1];
+            jumped = 1;
+            break;
+        default:
+            goto fail;
+        }
+    }
+ fail:
+    free(cv.list);
+    return NULL;
+}
+
+static int
+sar_decode_answer(struct sar_request *req, struct dns_header *hdr, unsigned char *buf, unsigned int size)
+{
+    struct dns_rr *rr;
+    unsigned int ii, rr_count, pos;
+    int res;
+
+    /* Skip over query section. */
+    for (ii = 0, pos = 12; ii < hdr->qdcount; ++ii) {
+        /* Skip over compressed names. */
+        while (1) {
+            if (pos >= size)
+                return 2;
+            if (!buf[pos])
+                break;
+            switch (buf[pos] & RES_SIZE_FLAGS) {
+            case RES_SF_LABEL:
+                pos += buf[pos] + 1;
+                break;
+            case RES_SF_POINTER:
+                if (pos + 1 >= size)
+                    return 2;
+                pos = (buf[pos] & ~RES_SIZE_FLAGS) << 8 | buf[pos+1];
+                if (pos >= size)
+                    return 3;
+                break;
+            default:
+                return 4;
+            }
+        }
+        /* Skip over null terminator, type and class part of question. */
+        pos += 5;
+    }
+
+    /* Parse each RR in the answer. */
+    rr_count = hdr->ancount + hdr->nscount + hdr->arcount;
+    rr = calloc(1, rr_count * sizeof(rr[0]));
+    for (ii = 0; ii < rr_count; ++ii) {
+        rr[ii].name = sar_extract_name(buf, size, &pos);
+        if (!rr[ii].name) {
+            res = 5;
+            goto out;
+        }
+        if (pos + 10 > size) {
+            res = 6;
+            goto out;
+        }
+        rr[ii].type = buf[pos+0] << 8 | buf[pos+1];
+        rr[ii].class = buf[pos+2] << 8 | buf[pos+3];
+        rr[ii].ttl = buf[pos+4] << 24 | buf[pos+5] << 16 | buf[pos+6] << 8 | buf[pos+7];
+        rr[ii].rdlength = buf[pos+8] << 8 | buf[pos+9];
+        rr[ii].rd_start = pos + 10;
+        pos = pos + rr[ii].rdlength + 10;
+        if (pos > size) {
+            res = 7;
+            goto out;
+        }
+    }
+    res = 0;
+    req->expiry = 0;
+    req->cb_ok(req, hdr, rr, buf, size);
+    if (!req->expiry) {
+        req->cb_ok = NULL;
+        req->cb_fail = NULL;
+        dict_remove(sar_requests, req->id_text);
+    }
+
+out:
+    while (ii > 0)
+        free(rr[--ii].name);
+    free(rr);
+    return res;
+}
+
+static const unsigned char *
+sar_extract_rdata(struct dns_rr *rr, unsigned int len, unsigned char *raw, unsigned int raw_size)
+{
+    if (len > rr->rdlength)
+        return NULL;
+    if (rr->rd_start + len > raw_size)
+        return NULL;
+    return raw + rr->rd_start;
+}
+
+static void
+sar_fd_readable(struct io_fd *fd)
+{
+    struct sockaddr_storage ss;
+    struct dns_header hdr;
+    struct sar_nameserver *ns;
+    struct sar_request *req;
+    unsigned char *buf;
+    socklen_t ss_len;
+    int res, rcode, buf_len;
+    char id_text[6];
+
+    assert(sar_fd == fd);
+    buf_len = conf.sar_edns0;
+    if (!buf_len)
+        buf_len = 512;
+    buf = alloca(buf_len);
+    ss_len = sizeof(ss);
+    res = recvfrom(sar_fd_fd, buf, buf_len, 0, (struct sockaddr*)&ss, &ss_len);
+    if (res < 12 || !(ns = sar_our_server(&ss, ss_len)))
+        return;
+    hdr.id = buf[0] << 8 | buf[1];
+    hdr.flags = buf[2] << 8 | buf[3];
+    hdr.qdcount = buf[4] << 8 | buf[5];
+    hdr.ancount = buf[6] << 8 | buf[7];
+    hdr.nscount = buf[8] << 8 | buf[9];
+    hdr.arcount = buf[10] << 8 | buf[11];
+
+    sprintf(id_text, "%d", hdr.id);
+    req = dict_find(sar_requests, id_text, NULL);
+    log_module(sar_log, LOG_DEBUG, "sar_fd_readable(%p): hdr {id=%d, flags=0x%x, qdcount=%d, ancount=%d, nscount=%d, arcount=%d} -> req %p", fd, hdr.id, hdr.flags, hdr.qdcount, hdr.ancount, hdr.nscount, hdr.arcount, req);
+    if (!req || !req->retries || !(hdr.flags & REQ_FLAG_QR)) {
+        ns->resp_ignored++;
+        return;
+    }
+    rcode = hdr.flags & REQ_FLAG_RCODE_MASK;
+    if (rcode != RCODE_NO_ERROR) {
+        sar_request_fail(req, rcode);
+    } else if (sar_decode_answer(req, &hdr, (unsigned char*)buf, res)) {
+        ns->resp_scrambled++;
+        sar_request_fail(req, RCODE_FORMAT_ERROR);
+    }
+}
+
+static void
+sar_build_nslist(struct string_list *nslist)
+{
+    dict_iterator_t it, next;
+    struct sar_nameserver *ns;
+    unsigned int ii;
+
+    for (it = dict_first(sar_nameservers); it; it = iter_next(it)) {
+        ns = iter_data(it);
+        ns->valid = 0;
+    }
+
+    for (ii = 0; ii < nslist->used; ++ii) {
+        const char *name;
+
+        name = nslist->list[ii];
+        ns = dict_find(sar_nameservers, name, NULL);
+        if (!ns) {
+            ns = calloc(1, sizeof(*ns) + strlen(name) + 1);
+            ns->name = (char*)(ns + 1);
+            strcpy(ns->name, name);
+            ns->ss_len = sizeof(ns->ss);
+            if (!sar_pton((struct sockaddr*)&ns->ss, sizeof(ns->ss), NULL, name)) {
+                free(it);
+                continue;
+            }
+            sar_set_port((struct sockaddr*)&ns->ss, sizeof(ns->ss), 53);
+            ns->ss_len = sar_helpers[ns->ss.ss_family]->socklen;
+            dict_insert(sar_nameservers, ns->name, ns);
+        }
+        ns->valid = 1;
+    }
+
+    for (it = dict_first(sar_nameservers); it; it = next) {
+        next = iter_next(it);
+        ns = iter_data(it);
+        if (!ns->valid)
+            dict_remove(sar_nameservers, ns->name);
+    }
+}
+
+static int
+sar_open_fd(void)
+{
+    int res;
+
+    /* Build list of nameservers. */
+    sar_build_nslist(conf.sar_nslist);
+
+    if (conf.sar_bind_address.ss_family != 0) {
+        struct addrinfo *ai;
+
+        ai = (struct addrinfo*)&conf.sar_bind_address;
+        sar_fd_fd = socket(ai->ai_family, SOCK_DGRAM, 0);
+        if (sar_fd_fd < 0) {
+            log_module(sar_log, LOG_FATAL, "Unable to create resolver socket: %s", strerror(errno));
+            return 1;
+        }
+
+        res = bind(sar_fd_fd, ai->ai_addr, ai->ai_addrlen);
+        if (res < 0)
+            log_module(sar_log, LOG_ERROR, "Unable to bind resolver socket to address [%s]:%s: %s", (char*)conf_get_data("modules/sar/bind_address", RECDB_QSTRING), (char*)conf_get_data("modules/sar/bind_port", RECDB_QSTRING), strerror(errno));
+    } else {
+        dict_iterator_t it;
+        struct sar_nameserver *ns;
+
+        it = dict_first(sar_nameservers);
+        ns = iter_data(it);
+        sar_fd_fd = socket(ns->ss.ss_family, SOCK_DGRAM, 0);
+        if (sar_fd_fd < 0) {
+            log_module(sar_log, LOG_FATAL, "Unable to create resolver socket: %s", strerror(errno));
+            return 1;
+        }
+    }
+
+    sar_fd = ioset_add(sar_fd_fd);
+    if (!sar_fd) {
+        log_module(sar_log, LOG_FATAL, "Unable to register resolver socket with event loop.");
+        return 1;
+    }
+    sar_fd->state = IO_CONNECTED;
+    sar_fd->readable_cb = sar_fd_readable;
+    return 0;
+}
+
+struct name_ofs {
+    const char *name;
+    unsigned int ofs;
+};
+
+static int
+set_compare_charp(const void *a_, const void *b_)
+{
+    char * const *a = a_, * const *b = b_;
+    return strcasecmp(*a, *b);
+}
+
+static void
+string_buffer_reserve(struct string_buffer *cv, unsigned int min_length)
+{
+    if (cv->size < min_length) {
+        char *new_buffer;
+        new_buffer = realloc(cv->list, min_length);
+        if (new_buffer) {
+            cv->size = min_length;
+            cv->list = new_buffer;
+        }
+    }
+}
+
+/** Append \a name to \a cv in compressed form. */
+static int
+sar_append_name(struct string_buffer *cv, const char *name, struct name_ofs *ofs, unsigned int *used, unsigned int alloc)
+{
+    struct name_ofs *pofs;
+    unsigned int len;
+
+    while (1) {
+        pofs = bsearch(&name, ofs, *used, sizeof(ofs[0]), set_compare_charp);
+        if (pofs) {
+            string_buffer_reserve(cv, cv->used + 2);
+            cv->list[cv->used++] = RES_SF_POINTER | (pofs->ofs >> 8);
+            cv->list[cv->used++] = pofs->ofs & 255;
+            return 0;
+        }
+        len = strcspn(name, ".");
+        if (len > 63)
+            return 1;
+        if (*used < alloc) {
+            ofs[*used].name = name;
+            ofs[*used].ofs = cv->used;
+            qsort(ofs, (*used)++, sizeof(ofs[0]), set_compare_charp);
+        }
+        string_buffer_reserve(cv, cv->used + len + 1);
+        cv->list[cv->used] = RES_SF_LABEL | len;
+        memcpy(cv->list + cv->used + 1, name, len);
+        cv->used += len + 1;
+        if (name[len] == '.')
+            name += len + 1;
+        else if (name[len] == '\0')
+            break;
+    }
+    string_buffer_append(cv, '\0');
+    return 0;
+}
+
+/** Build a DNS question packet from a variable-length argument list.
+ * In \a args, there is at least one pari consisting of const char
+ * *name and unsigned int qtype.  A null name argument terminates the
+ * list.
+ */
+unsigned int
+sar_request_vbuild(struct sar_request *req, va_list args)
+{
+    struct name_ofs suffixes[32];
+    struct string_buffer cv;
+    const char *name;
+    unsigned int suf_used;
+    unsigned int val;
+    unsigned int qdcount;
+
+    cv.used = 0;
+    cv.size = 512;
+    cv.list = calloc(1, cv.size);
+    suf_used = 0;
+    val = REQ_OPCODE_QUERY | REQ_FLAG_RD;
+    cv.list[0] = req->id >> 8;
+    cv.list[1] = req->id & 255;
+    cv.list[2] = val >> 8;
+    cv.list[3] = val & 255;
+    cv.list[6] = cv.list[7] = cv.list[8] = cv.list[9] = cv.list[10] = 0;
+    cv.used = 12;
+    for (qdcount = 0; (name = va_arg(args, const char*)); ++qdcount) {
+        if (sar_append_name(&cv, name, suffixes, &suf_used, ArrayLength(suffixes))) {
+            string_buffer_clean(&cv);
+            goto out;
+        }
+        string_buffer_reserve(&cv, cv.used + 4);
+        val = va_arg(args, unsigned int);
+        cv.list[cv.used++] = val >> 8;
+        cv.list[cv.used++] = val & 255;
+        cv.list[cv.used++] = REQ_CLASS_IN >> 8;
+        cv.list[cv.used++] = REQ_CLASS_IN & 255;
+    }
+    cv.list[4] = qdcount >> 8;
+    cv.list[5] = qdcount & 255;
+    val = conf.sar_edns0;
+    if (val) {
+        string_buffer_reserve(&cv, cv.used + 11);
+        cv.list[cv.used +  0] = '\0'; /* empty name */
+        cv.list[cv.used +  1] = REQ_TYPE_OPT >> 8;
+        cv.list[cv.used +  2] = REQ_TYPE_OPT & 255;
+        cv.list[cv.used +  3] = val >> 8;
+        cv.list[cv.used +  4] = val & 255;
+        cv.list[cv.used +  5] = 0; /* extended-rcode */
+        cv.list[cv.used +  6] = 0; /* version */
+        cv.list[cv.used +  7] = 0; /* reserved */
+        cv.list[cv.used +  8] = 0; /* reserved */
+        cv.list[cv.used +  9] = 0; /* msb rdlen */
+        cv.list[cv.used + 10] = 0; /* lsb rdlen */
+        cv.used += 11;
+        cv.list[11] = 1; /* update arcount */
+    } else cv.list[11] = 0;
+
+out:
+    free(req->body);
+    req->body = (unsigned char*)cv.list;
+    req->body_len = cv.used;
+    return cv.used;
+}
+
+/** Build a DNS question packet.  After \a req, there is at least one
+ * pair consisting of const char *name and unsigned int qtype.  A null
+ * name argument terminates the list.
+ */
+unsigned int
+sar_request_build(struct sar_request *req, ...)
+{
+    va_list vargs;
+    unsigned int ret;
+    va_start(vargs, req);
+    ret = sar_request_vbuild(req, vargs);
+    va_end(vargs);
+    return ret;
+}
+
+void
+sar_request_send(struct sar_request *req)
+{
+    dict_iterator_t it;
+
+    /* make sure we have our local socket */
+    if (!sar_fd && sar_open_fd()) {
+        sar_request_fail(req, RCODE_SOCKET_FAILURE);
+        return;
+    }
+
+    log_module(sar_log, LOG_DEBUG, "sar_request_send({id=%d})", req->id);
+
+    /* send query to each configured nameserver */
+    for (it = dict_first(sar_nameservers); it; it = iter_next(it)) {
+        struct sar_nameserver *ns;
+        int res;
+
+        ns = iter_data(it);
+        res = sendto(sar_fd_fd, req->body, req->body_len, 0, (struct sockaddr*)&ns->ss, ns->ss_len);
+        if (res > 0) {
+            ns->req_sent++;
+            log_module(sar_log, LOG_DEBUG, "Sent %u bytes to %s.", res, ns->name);
+        } else if (res < 0)
+            log_module(sar_log, LOG_ERROR, "Unable to send %u bytes to nameserver %s: %s", req->body_len, ns->name, strerror(errno));
+        else /* res == 0 */
+            assert(0 && "resolver sendto() unexpectedly returned zero");
+    }
+
+    /* Check that query timeout is soon enough. */
+    req->expiry = now + (conf.sar_timeout << ++req->retries);
+    sar_check_timeout(req->expiry);
+}
+
+struct sar_request *
+sar_request_alloc(unsigned int data_len, sar_request_ok_cb ok_cb, sar_request_fail_cb fail_cb)
+{
+    struct sar_request *req;
+
+    req = calloc(1, sizeof(*req) + data_len);
+    req->cb_ok = ok_cb;
+    req->cb_fail = fail_cb;
+    do {
+        req->id = rand() & 0xffff;
+        sprintf(req->id_text, "%d", req->id);
+    } while (dict_find(sar_requests, req->id_text, NULL));
+    dict_insert(sar_requests, req->id_text, req);
+    log_module(sar_log, LOG_DEBUG, "sar_request_alloc(%d) -> {id=%d}", data_len, req->id);
+    return req;
+}
+
+struct sar_request *
+sar_request_simple(unsigned int data_len, sar_request_ok_cb ok_cb, sar_request_fail_cb fail_cb, ...)
+{
+    struct sar_request *req;
+
+    req = sar_request_alloc(data_len, ok_cb, fail_cb);
+    if (req) {
+        va_list args;
+
+        va_start(args, fail_cb);
+        sar_request_vbuild(req, args);
+        va_end(args);
+        sar_request_send(req);
+    }
+    return req;
+}
+
+enum service_proto {
+    SERVICE_UDP,
+    SERVICE_TCP,
+    SERVICE_NUM_PROTOS
+};
+
+struct service_byname {
+    const char *name; /* service name */
+    struct {
+        /* note: if valid != 0, port == 0, check canonical entry */
+        struct service_byname *canon; /* if NULL, this is canonical */
+        uint16_t port;
+        unsigned int valid : 1;
+        unsigned int srv : 1;
+    } protos[SERVICE_NUM_PROTOS];
+};
+
+struct service_byport {
+    unsigned int port;
+    char port_text[6];
+    struct service_byname *byname[SERVICE_NUM_PROTOS];
+};
+
+static dict_t services_byname; /* contains struct service_byname */
+static dict_t services_byport; /* contains struct service_byport */
+
+static struct service_byname *
+sar_service_byname(const char *name, int autocreate)
+{
+    struct service_byname *byname;
+
+    byname = dict_find(services_byname, name, NULL);
+    if (!byname && autocreate) {
+        byname = calloc(1, sizeof(*byname) + strlen(name) + 1);
+        byname->name = strcpy((char*)(byname + 1), name);
+        dict_insert(services_byname, byname->name, byname);
+    }
+    return byname;
+}
+
+static struct service_byport *
+sar_service_byport(unsigned int port, int autocreate)
+{
+    struct service_byport *byport;
+    char port_text[12];
+
+    sprintf(port_text, "%d", port);
+    byport = dict_find(services_byport, port_text, NULL);
+    if (!byport && autocreate) {
+        byport = calloc(1, sizeof(*byport));
+        byport->port = port;
+        sprintf(byport->port_text, "%d", port);
+        dict_insert(services_byport, byport->port_text, byport);
+    }
+    return byport;
+}
+
+static void
+sar_services_load_file(const char *etc_services)
+{
+    static const char *whitespace = " \t\r\n";
+    struct service_byname *canon;
+    struct service_byport *byport;
+    char *name, *port, *alias, *ptr;
+    FILE *file;
+    unsigned int pnum;
+    enum service_proto proto;
+    char linebuf[LINE_MAX];
+
+    file = fopen(etc_services, "r");
+    if (!file)
+        return;
+    while (fgets(linebuf, sizeof(linebuf), file)) {
+        ptr = strchr(linebuf, '#');
+        if (ptr)
+            *ptr = '\0';
+        /* Tokenize canonical service name and port number. */
+        name = strtok_r(linebuf, whitespace, &ptr);
+        if (name == NULL)
+            continue;
+        port = strtok_r(NULL, whitespace, &ptr);
+        if (port == NULL)
+            continue;
+        pnum = strtoul(port, &port, 10);
+        if (pnum == 0 || *port++ != '/')
+            continue;
+        if (!strcmp(port, "udp"))
+            proto = SERVICE_UDP;
+        else if (!strcmp(port, "tcp"))
+            proto = SERVICE_TCP;
+        else continue;
+
+        /* Set up canonical name-indexed service entry. */
+        canon = sar_service_byname(name, 1);
+        if (canon->protos[proto].valid) {
+            log_module(sar_log, LOG_ERROR, "Service %s/%s listed twice.", name, port);
+            continue;
+        }
+        canon->protos[proto].canon = NULL;
+        canon->protos[proto].port = pnum;
+        canon->protos[proto].valid = 1;
+
+        /* Set up port-indexed service entry. */
+        byport = sar_service_byport(pnum, 1);
+        if (!byport->byname[proto])
+            byport->byname[proto] = canon;
+
+        /* Add alias entries. */
+        while ((alias = strtok_r(NULL, whitespace, &ptr))) {
+            struct service_byname *byname;
+
+            byname = sar_service_byname(alias, 1);
+            if (byname->protos[proto].valid) {
+                /* We do not log this since there are a lot of
+                 * duplicate aliases, some only differing in case. */
+                continue;
+            }
+            byname->protos[proto].canon = canon;
+            byname->protos[proto].port = pnum;
+            byname->protos[proto].valid = 1;
+        }
+    }
+    fclose(file);
+}
+
+static void
+sar_services_init(const char *etc_services)
+{
+    /* These are a portion of the services listed at
+     * http://www.dns-sd.org/ServiceTypes.html.
+     */
+    static const char *tcp_srvs[] = { "cvspserver", "distcc", "ftp", "http",
+        "imap", "ipp", "irc", "ldap", "login", "nfs", "pop3", "postgresql",
+        "rsync", "sftp-ssh", "soap", "ssh", "telnet", "webdav", "xmpp-client",
+        "xmpp-server", "xul-http", NULL };
+    static const char *udp_srvs[] = { "bootps", "dns-update", "domain", "nfs",
+        "ntp", "tftp", NULL };
+    struct service_byname *byname;
+    unsigned int ii;
+
+    sar_services_load_file(etc_services);
+
+    for (ii = 0; tcp_srvs[ii]; ++ii) {
+        byname = sar_service_byname(tcp_srvs[ii], 1);
+        byname->protos[SERVICE_TCP].srv = 1;
+    }
+
+    for (ii = 0; udp_srvs[ii]; ++ii) {
+        byname = sar_service_byname(udp_srvs[ii], 1);
+        byname->protos[SERVICE_UDP].srv = 1;
+    }
+}
+
+static void
+sar_register_helper(struct sar_family_helper *helper)
+{
+    assert(helper->family <= MAX_FAMILY);
+    sar_helpers[helper->family] = helper;
+    helper->next = sar_first_helper;
+    sar_first_helper = helper;
+}
+
+static unsigned int
+sar_addrlen(const struct sockaddr *sa, UNUSED_ARG(unsigned int size))
+{
+    return sa->sa_family <= MAX_FAMILY && sar_helpers[sa->sa_family]
+        ? sar_helpers[sa->sa_family]->socklen : 0;
+}
+
+struct sar_getaddr_state {
+    struct sar_family_helper *helper;
+    struct addrinfo *ai_head;
+    struct addrinfo *ai_tail;
+    sar_addr_cb cb;
+    void *cb_ctx;
+    unsigned int search_pos;
+    unsigned int flags, socktype, protocol, port;
+    unsigned int srv_ofs;
+    char full_name[DNS_NAME_LENGTH];
+};
+
+static unsigned int
+sar_getaddr_append(struct sar_getaddr_state *state, struct addrinfo *ai, int copy)
+{
+    unsigned int count;
+
+    log_module(sar_log, LOG_DEBUG, "sar_getaddr_append({full_name=%s}, ai=%p, copy=%d)", state->full_name, ai, copy);
+
+    /* Set the appropriate pointer to the new element(s). */
+    if (state->ai_tail)
+        state->ai_tail->ai_next = ai;
+    else
+        state->ai_head = ai;
+
+    /* Find the end of the list. */
+    if (copy) {
+        /* Make sure we copy fields for both the first and last entries. */
+        count = 1;
+        while (1) {
+            if (!ai->ai_addrlen) {
+                assert(sar_helpers[ai->ai_family]);
+                ai->ai_addrlen = sar_helpers[ai->ai_family]->socklen;
+            }
+#if defined(HAVE_SOCKADDR_SA_LEN)
+            ai->ai_addr->sa_len = ai->ai_addrlen;
+#endif
+            ai->ai_addr->sa_family = ai->ai_family;
+            ai->ai_socktype = state->socktype;
+            ai->ai_protocol = state->protocol;
+            if (!ai->ai_next)
+                break;
+            count++;
+            ai = ai->ai_next;
+        }
+    } else {
+        for (count = 1; ai->ai_next; ++count, ai = ai->ai_next)
+            ;
+    }
+
+    /* Set the tail pointer and return count of appended items. */
+    state->ai_tail = ai;
+    return count;
+}
+
+static struct sar_request *
+sar_getaddr_request(struct sar_request *req)
+{
+    struct sar_getaddr_state *state;
+    unsigned int len;
+    char full_name[DNS_NAME_LENGTH];
+
+    state = (struct sar_getaddr_state*)(req + 1);
+
+    /* If we can and should, append the current search domain. */
+    if (state->search_pos < conf.sar_search->used)
+        snprintf(full_name, sizeof(full_name), "%s.%s", state->full_name, conf.sar_search->list[state->search_pos]);
+    else if (state->search_pos == conf.sar_search->used)
+        safestrncpy(full_name, state->full_name, sizeof(full_name));
+    else {
+        log_module(sar_log, LOG_DEBUG, "sar_getaddr_request({id=%d}): failed", req->id);
+        state->cb(state->cb_ctx, NULL, SAI_NONAME);
+        return NULL;
+    }
+
+    /* Build the appropriate request for DNS record(s). */
+    if (state->flags & SAI_ALL)
+        len = sar_request_build(req, full_name + state->srv_ofs, REQ_QTYPE_ALL, NULL);
+    else if (state->srv_ofs)
+        len = state->helper->build_addr_request(req, full_name + state->srv_ofs, full_name, state->flags);
+    else
+        len = state->helper->build_addr_request(req, full_name, NULL, state->flags);
+
+    log_module(sar_log, LOG_DEBUG, "sar_getaddr_request({id=%d}): full_name=%s, srv_ofs=%d", req->id, full_name, state->srv_ofs);
+
+    /* Check that the request could be built. */
+    if (!len) {
+        state->cb(state->cb_ctx, NULL, SAI_NODATA);
+        return NULL;
+    }
+
+    /* Send the request. */
+    sar_request_send(req);
+    return req;
+}
+
+static int
+sar_getaddr_decode(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size, unsigned int rr_idx)
+{
+    struct sar_getaddr_state *state;
+    char *cname;
+    unsigned int jj, pos, hit;
+
+    log_module(sar_log, LOG_DEBUG, "  sar_getaddr_decode(id=%d, <hdr>, {type=%d, rdlength=%d, name=%s}, <data>, %u, <idx>)", hdr->id, rr[rr_idx].type, rr[rr_idx].rdlength, rr[rr_idx].name, raw_size);
+    state = (struct sar_getaddr_state*)(req + 1);
+
+    switch (rr[rr_idx].type) {
+    case REQ_TYPE_A:
+        if (state->flags & SAI_ALL)
+            return sar_ipv4_helper.decode_addr(state, rr + rr_idx, raw, raw_size);
+#if defined(AF_INET6)
+        else if (state->flags & SAI_V4MAPPED)
+            return sar_ipv6_helper.decode_addr(state, rr + rr_idx, raw, raw_size);
+#endif
+        return state->helper->decode_addr(state, rr + rr_idx, raw, raw_size);
+
+    case REQ_TYPE_AAAA:
+#if defined(AF_INET6)
+        if (state->flags & SAI_ALL)
+            return sar_ipv6_helper.decode_addr(state, rr + rr_idx, raw, raw_size);
+        return state->helper->decode_addr(state, rr + rr_idx, raw, raw_size);
+#else
+        return 0;
+#endif
+
+    case REQ_TYPE_CNAME:
+        /* there should be the canonical name next */
+        pos = rr[rr_idx].rd_start;
+        cname = sar_extract_name(raw, raw_size, &pos);
+        if (!cname)
+            return 0; /* XXX: eventually log the unhandled body */
+        /* and it should correspond to some other answer in the response */
+        for (jj = hit = 0; jj < hdr->ancount; ++jj) {
+            if (strcasecmp(cname, rr[jj].name))
+                continue;
+            hit += sar_getaddr_decode(req, hdr, rr, raw, raw_size, jj);
+        }
+        /* XXX: if (!hit) handle or log the incomplete recursion; */
+        return hit;
+
+    case REQ_TYPE_SRV:
+        /* TODO: decode the SRV record */
+
+    default:
+        return 0;
+    }
+}
+
+static void
+sar_getaddr_ok(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size)
+{
+    struct sar_getaddr_state *state;
+    unsigned int ii;
+
+    state = (struct sar_getaddr_state*)(req + 1);
+
+    log_module(sar_log, LOG_DEBUG, "sar_getaddr_ok({id=%d}, {id=%d}, <rr>, <data>, %u)", req->id, hdr->id, raw_size);
+    for (ii = 0; ii < hdr->ancount; ++ii)
+        sar_getaddr_decode(req, hdr, rr, raw, raw_size, ii);
+
+    /* If we found anything, report it, else try again. */
+    if (state->ai_head)
+        state->cb(state->cb_ctx, state->ai_head, SAI_SUCCESS);
+    else
+        sar_getaddr_request(req);
+}
+
+static void
+sar_getaddr_fail(struct sar_request *req, UNUSED_ARG(unsigned int rcode))
+{
+    struct sar_getaddr_state *state;
+
+    log_module(sar_log, LOG_DEBUG, "sar_getaddr_fail({id=%d}, rcode=%u)", req->id, rcode);
+    state = (struct sar_getaddr_state*)(req + 1);
+    state->cb(state->cb_ctx, NULL, SAI_FAIL);
+}
+
+struct sar_request *
+sar_getaddr(const char *node, const char *service, const struct addrinfo *hints_, sar_addr_cb cb, void *cb_ctx)
+{
+    struct sockaddr_storage ss;
+    struct addrinfo hints;
+    struct sar_family_helper *helper;
+    struct service_byname *svc;
+    char *end;
+    unsigned int portnum;
+    unsigned int pos;
+    enum service_proto proto;
+
+    if (!node && !service) {
+        cb(cb_ctx, NULL, SAI_NONAME);
+        return NULL;
+    }
+
+    /* Initialize local hints structure. */
+    if (hints_)
+        memcpy(&hints, hints_, sizeof(hints));
+    else
+        memset(&hints, 0, sizeof(hints));
+
+    /* Translate socket type to internal protocol. */
+    switch (hints.ai_socktype) {
+    case 0: hints.ai_socktype = SOCK_STREAM; /* and fall through */
+    case SOCK_STREAM: proto = SERVICE_TCP; break;
+    case SOCK_DGRAM: proto = SERVICE_UDP; break;
+    default:
+        cb(cb_ctx, NULL, SAI_SOCKTYPE);
+        return NULL;
+    }
+
+    /* Figure out preferred socket size. */
+    if (hints.ai_family == AF_UNSPEC)
+        hints.ai_family = AF_INET;
+    if (hints.ai_family > MAX_FAMILY
+        || !(helper = sar_helpers[hints.ai_family])) {
+        cb(cb_ctx, NULL, SAI_FAMILY);
+        return NULL;
+    }
+    hints.ai_addrlen = helper->socklen;
+
+    /* If \a node is NULL, figure out the correct default from the
+     * requested family and SAI_PASSIVE flag.
+     */
+    if (node == NULL)
+        node = (hints.ai_flags & SAI_PASSIVE) ? helper->unspec_addr : helper->localhost_addr;
+
+    /* Try to parse (failing that, look up) \a service. */
+    if (!service)
+        portnum = 0, svc = NULL;
+    else if ((portnum = strtoul(service, &end, 10)), *end == '\0')
+        svc = NULL;
+    else if ((svc = sar_service_byname(service, 0)) != NULL)
+        portnum = svc->protos[proto].port;
+    else {
+        cb(cb_ctx, NULL, SAI_SERVICE);
+        return NULL;
+    }
+
+    /* Try to parse \a node as a numeric hostname.*/
+    pos = sar_pton((struct sockaddr*)&ss, sizeof(ss), NULL, node);
+    if (pos && node[pos] == '\0') {
+        struct addrinfo *ai;
+        char canonname[SAR_NTOP_MAX];
+
+        /* we have a valid address; use it */
+        sar_set_port((struct sockaddr*)&ss, sizeof(ss), portnum);
+        hints.ai_addrlen = sar_addrlen((struct sockaddr*)&ss, sizeof(ss));
+        if (!hints.ai_addrlen) {
+            cb(cb_ctx, NULL, SAI_FAMILY);
+            return NULL;
+        }
+        pos = sar_ntop(canonname, sizeof(canonname), (struct sockaddr*)&ss, hints.ai_addrlen);
+
+        /* allocate and fill in the addrinfo response */
+        ai = calloc(1, sizeof(*ai) + hints.ai_addrlen + pos + 1);
+        ai->ai_family = ss.ss_family;
+        ai->ai_socktype = hints.ai_socktype;
+        ai->ai_protocol = hints.ai_protocol;
+        ai->ai_addrlen = hints.ai_addrlen;
+        ai->ai_addr = memcpy(ai + 1, &ss, ai->ai_addrlen);
+        ai->ai_canonname = strcpy((char*)ai->ai_addr + ai->ai_addrlen, canonname);
+        cb(cb_ctx, ai, SAI_SUCCESS);
+        return NULL;
+    } else if (hints.ai_flags & SAI_NUMERICHOST) {
+        cb(cb_ctx, NULL, SAI_NONAME);
+        return NULL;
+    } else {
+        struct sar_request *req;
+        struct sar_getaddr_state *state;
+        unsigned int len, ii;
+
+        req = sar_request_alloc(sizeof(*state), sar_getaddr_ok, sar_getaddr_fail);
+
+        state = (struct sar_getaddr_state*)(req + 1);
+        state->helper = helper;
+        state->ai_head = state->ai_tail = NULL;
+        state->cb = cb;
+        state->cb_ctx = cb_ctx;
+        state->flags = hints.ai_flags;
+        state->socktype = hints.ai_socktype;
+        state->protocol = hints.ai_protocol;
+        state->port = portnum;
+
+        if ((state->flags & SAI_NOSRV) || !svc)
+            state->srv_ofs = 0;
+        else if (svc->protos[proto].srv)
+            state->srv_ofs = snprintf(state->full_name, sizeof(state->full_name), "_%s._%s.", svc->name, (proto == SERVICE_UDP ? "udp" : "tcp"));
+        else if (state->flags & SAI_FORCESRV)
+            state->srv_ofs = snprintf(state->full_name, sizeof(state->full_name), "_%s._%s.", service, (proto == SERVICE_UDP ? "udp" : "tcp"));
+        else
+            state->srv_ofs = 0;
+
+        if (state->srv_ofs < sizeof(state->full_name))
+            safestrncpy(state->full_name + state->srv_ofs, node, sizeof(state->full_name) - state->srv_ofs);
+
+        for (ii = len = 0; node[ii]; ++ii)
+            if (node[ii] == '.')
+                len++;
+        if (len >= conf.sar_ndots)
+            state->search_pos = conf.sar_search->used;
+        else
+            state->search_pos = 0;
+
+        /* XXX: fill in *state with any other fields needed to parse responses. */
+
+        if (!sar_getaddr_request(req)) {
+            free(req);
+            return NULL;
+        }
+        return req;
+    }
+}
+
+struct sar_getname_state {
+    sar_name_cb cb;
+    void *cb_ctx;
+    char *hostname;
+    unsigned int flags;
+    unsigned int family;
+    enum service_proto proto;
+    unsigned short port;
+    unsigned int doing_arpa : 1; /* checking .ip6.arpa vs .ip6.int */
+    unsigned char original[16]; /* original address data */
+    /* name must be long enough to hold "0.0.<etc>.ip6.arpa" */
+    char name[74];
+};
+
+static void
+sar_getname_fail(struct sar_request *req, UNUSED_ARG(unsigned int rcode))
+{
+    struct sar_getname_state *state;
+    unsigned int len;
+
+    state = (struct sar_getname_state*)(req + 1);
+    if (state->doing_arpa) {
+        len = strlen(state->name);
+        assert(len == 73);
+        strcpy(state->name + len - 4, "int");
+        len = sar_request_build(req, state->name, REQ_TYPE_PTR, NULL);
+        if (len) {
+            sar_request_send(req);
+            return;
+        }
+    }
+    state->cb(state->cb_ctx, NULL, NULL, SAI_FAIL);
+    free(state->hostname);
+}
+
+static const char *sar_getname_port(unsigned int port, unsigned int flags, char *tmpbuf, unsigned int tmpbuf_len)
+{
+    struct service_byport *service;
+    enum service_proto proto;
+    char port_text[12];
+
+    sprintf(port_text, "%d", port);
+    proto = (flags & SNI_DGRAM) ? SERVICE_UDP : SERVICE_TCP;
+    if (!(flags & SNI_NUMERICSERV)
+        && (service = dict_find(services_byport, port_text, NULL))
+        && service->byname[proto])
+        return service->byname[proto]->name;
+    snprintf(tmpbuf, tmpbuf_len, "%d", port);
+    return tmpbuf;
+}
+
+static void
+sar_getname_confirm(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size)
+{
+    struct sar_getname_state *state;
+    const unsigned char *data;
+    const char *portname;
+    char servbuf[16];
+    unsigned int ii, nbr;
+
+    state = (struct sar_getname_state*)(req + 1);
+    for (ii = 0; ii < hdr->ancount; ++ii) {
+        /* Is somebody confused or trying to play games? */
+        if (rr[ii].class != REQ_CLASS_IN
+            || strcasecmp(state->hostname, rr[ii].name))
+            continue;
+        switch (rr[ii].type) {
+        case REQ_TYPE_A: nbr = 4; break;
+        case REQ_TYPE_AAAA: nbr = 16; break;
+        default: continue;
+        }
+        data = sar_extract_rdata(rr, nbr, raw, raw_size);
+        if (data && !memcmp(data, state->original, nbr)) {
+            portname = sar_getname_port(state->port, state->flags, servbuf, sizeof(servbuf));
+            state->cb(state->cb_ctx, state->hostname, portname, SAI_SUCCESS);
+            free(state->hostname);
+            return;
+        }
+    }
+    state->cb(state->cb_ctx, NULL, NULL, SAI_MISMATCH);
+    free(state->hostname);
+}
+
+static void
+sar_getname_ok(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size)
+{
+    struct sar_getname_state *state;
+    const char *portname;
+    unsigned int ii, pos;
+    char servbuf[16];
+
+    state = (struct sar_getname_state*)(req + 1);
+    for (ii = 0; ii < hdr->ancount; ++ii) {
+        if (rr[ii].type != REQ_TYPE_PTR
+            || rr[ii].class != REQ_CLASS_IN
+            || strcasecmp(rr[ii].name, state->name))
+            continue;
+        pos = rr[ii].rd_start;
+        state->hostname = sar_extract_name(raw, raw_size, &pos);
+        break;
+    }
+
+    if (!state->hostname) {
+        state->cb(state->cb_ctx, NULL, NULL, SAI_NONAME);
+        return;
+    }
+
+    if (state->flags & SNI_PARANOID) {
+        req->cb_ok = sar_getname_confirm;
+        pos = sar_helpers[state->family]->build_addr_request(req, state->hostname, NULL, 0);
+        if (pos)
+            sar_request_send(req);
+        else {
+            free(state->hostname);
+            state->cb(state->cb_ctx, NULL, NULL, SAI_FAIL);
+        }
+        return;
+    }
+
+    portname = sar_getname_port(state->port, state->flags, servbuf, sizeof(servbuf));
+    state->cb(state->cb_ctx, state->hostname, portname, SAI_SUCCESS);
+    free(state->hostname);
+}
+
+struct sar_request *
+sar_getname(const struct sockaddr *sa, unsigned int salen, int flags, sar_name_cb cb, void *cb_ctx)
+{
+    struct sar_family_helper *helper;
+    struct sar_request *req;
+    struct sar_getname_state *state;
+    unsigned int len;
+    int port;
+
+    if (sa->sa_family > MAX_FAMILY
+        || !(helper = sar_helpers[sa->sa_family])) {
+        cb(cb_ctx, NULL, NULL, SAI_FAMILY);
+        return NULL;
+    }
+
+    port = helper->get_port(sa, salen);
+
+    if (flags & SNI_NUMERICHOST) {
+        const char *servname;
+        unsigned int len;
+        char host[SAR_NTOP_MAX], servbuf[16];
+
+        /* If appropriate, try to look up service name. */
+        servname = sar_getname_port(port, flags, servbuf, sizeof(servbuf));
+        len = sar_ntop(host, sizeof(host), sa, salen);
+        assert(len != 0);
+        cb(cb_ctx, host, servname, SAI_SUCCESS);
+        return NULL;
+    }
+
+    req = sar_request_alloc(sizeof(*state), sar_getname_ok, sar_getname_fail);
+
+    state = (struct sar_getname_state*)(req + 1);
+    state->cb = cb;
+    state->cb_ctx = cb_ctx;
+    state->flags = flags;
+    state->family = sa->sa_family;
+    state->port = port;
+
+    helper->build_ptr_name(state, sa, salen);
+    assert(strlen(state->name) < sizeof(state->name));
+    len = sar_request_build(req, state->name, REQ_TYPE_PTR, NULL);
+    if (!len) {
+        cb(cb_ctx, NULL, NULL, SAI_NODATA);
+        free(req);
+        return NULL;
+    }
+
+    sar_request_send(req);
+    return req;
+}
+
+static unsigned int
+ipv4_ntop(char *output, unsigned int out_size, const struct sockaddr *sa, UNUSED_ARG(unsigned int socklen))
+{
+    struct sockaddr_in *sin;
+    unsigned int ip4, pos;
+
+    sin = (struct sockaddr_in*)sa;
+    ip4 = ntohl(sin->sin_addr.s_addr);
+    pos = snprintf(output, out_size, "%u.%u.%u.%u", (ip4 >> 24), (ip4 >> 16) & 255, (ip4 >> 8) & 255, ip4 & 255);
+    return (pos < out_size) ? pos : 0;
+}
+
+static unsigned int
+sar_pton_ip4(const char *input, unsigned int *bits, uint32_t *output)
+{
+    unsigned int dots = 0, pos = 0, part = 0, ip = 0;
+
+    /* Intentionally no support for bizarre IPv4 formats (plain
+     * integers, octal or hex components) -- only vanilla dotted
+     * decimal quads, optionally with trailing /nn.
+     */
+    if (input[0] == '.')
+        return 0;
+    while (1) {
+        if (isdigit(input[pos])) {
+            part = part * 10 + input[pos++] - '0';
+            if (part > 255)
+                return 0;
+            if ((dots == 3) && !isdigit(input[pos])) {
+                *output = htonl(ip | part);
+                return pos;
+            }
+        } else if (input[pos] == '.') {
+            if (input[++pos] == '.')
+                return 0;
+            ip |= part << (24 - 8 * dots++);
+            part = 0;
+        } else if (bits && input[pos] == '/' && isdigit(input[pos + 1])) {
+            unsigned int len;
+            char *term;
+
+            len = strtoul(input + pos + 1, &term, 10);
+            if (term <= input + pos + 1)
+                return pos;
+            else if (len > 32)
+                return 0;
+            *bits = len;
+            return term - input;
+        } else return 0;
+    }
+}
+
+static unsigned int
+ipv4_pton(struct sockaddr *sa, UNUSED_ARG(unsigned int socklen), unsigned int *bits, const char *input)
+{
+    unsigned int pos;
+
+    pos = sar_pton_ip4(input, bits, &((struct sockaddr_in*)sa)->sin_addr.s_addr);
+    if (!pos)
+        return 0;
+    sa->sa_family = AF_INET;
+    return pos;
+}
+
+static int
+ipv4_get_port(const struct sockaddr *sa, UNUSED_ARG(unsigned int socklen))
+{
+    return ntohs(((const struct sockaddr_in*)sa)->sin_port);
+}
+
+static int
+ipv4_set_port(struct sockaddr *sa, UNUSED_ARG(unsigned int socklen), unsigned short port)
+{
+    ((struct sockaddr_in*)sa)->sin_port = htons(port);
+    return 0;
+}
+
+static unsigned int
+ipv4_addr_request(struct sar_request *req, const char *node, const char *srv_node, UNUSED_ARG(unsigned int flags))
+{
+    unsigned int len;
+    if (srv_node)
+        len = sar_request_build(req, node, REQ_TYPE_A, srv_node, REQ_TYPE_SRV, NULL);
+    else
+        len = sar_request_build(req, node, REQ_TYPE_A, NULL);
+    return len;
+}
+
+static void
+ipv4_ptr_name(struct sar_getname_state *state, const struct sockaddr *sa, UNUSED_ARG(unsigned int socklen))
+{
+    const uint8_t *bytes;
+
+    bytes = (uint8_t*)&((struct sockaddr_in*)sa)->sin_addr.s_addr;
+    memcpy(state->original, bytes, 4);
+    snprintf(state->name, sizeof(state->name),
+             "%u.%u.%u.%u.in-addr.arpa",
+             bytes[3], bytes[2], bytes[1], bytes[0]);
+}
+
+static int
+ipv4_decode(struct sar_getaddr_state *state, struct dns_rr *rr, unsigned char *raw, UNUSED_ARG(unsigned int raw_size))
+{
+    struct sockaddr_in *sa;
+    struct addrinfo *ai;
+
+    if (rr->rdlength != 4)
+        return 0;
+
+    if (state->flags & SAI_CANONNAME) {
+        ai = calloc(1, sizeof(*ai) + sizeof(*sa) + strlen(rr->name) + 1);
+        sa = (struct sockaddr_in*)(ai->ai_addr = (struct sockaddr*)(ai + 1));
+        ai->ai_canonname = strcpy((char*)(sa + 1), rr->name);
+    } else {
+        ai = calloc(1, sizeof(*ai) + sizeof(*sa));
+        sa = (struct sockaddr_in*)(ai->ai_addr = (struct sockaddr*)(ai + 1));
+        ai->ai_canonname = NULL;
+    }
+
+    ai->ai_family = AF_INET;
+    sa->sin_port = htons(state->port);
+    memcpy(&sa->sin_addr.s_addr, raw + rr->rd_start, 4);
+    return sar_getaddr_append(state, ai, 1);
+}
+
+static struct sar_family_helper sar_ipv4_helper = {
+    "127.0.0.1",
+    "0.0.0.0",
+    sizeof(struct sockaddr_in),
+    AF_INET,
+    ipv4_ntop,
+    ipv4_pton,
+    ipv4_get_port,
+    ipv4_set_port,
+    ipv4_addr_request,
+    ipv4_ptr_name,
+    ipv4_decode,
+    NULL
+};
+
+#if defined(AF_INET6)
+
+static unsigned int
+ipv6_ntop(char *output, unsigned int out_size, const struct sockaddr *sa, UNUSED_ARG(unsigned int socklen))
+{
+    struct sockaddr_in6 *sin6;
+    unsigned int pos, part, max_start, max_zeros, curr_zeros, ii;
+    unsigned short addr16;
+
+    sin6 = (struct sockaddr_in6*)sa;
+    /* Find longest run of zeros. */
+    for (max_start = max_zeros = curr_zeros = ii = 0; ii < 8; ++ii) {
+        addr16 = (sin6->sin6_addr.s6_addr[ii * 2] << 8) | sin6->sin6_addr.s6_addr[ii * 2 + 1];
+        if (!addr16)
+            curr_zeros++;
+        else if (curr_zeros > max_zeros) {
+            max_start = ii - curr_zeros;
+            max_zeros = curr_zeros;
+            curr_zeros = 0;
+        }
+    }
+    if (curr_zeros > max_zeros) {
+        max_start = ii - curr_zeros;
+        max_zeros = curr_zeros;
+    }
+
+    /* Print out address. */
+#define APPEND(CH) do { output[pos++] = (CH); if (pos >= out_size) return 0; } while (0)
+    for (pos = 0, ii = 0; ii < 8; ++ii) {
+        if ((max_zeros > 0) && (ii == max_start)) {
+            if (ii == 0)
+                APPEND(':');
+            APPEND(':');
+            ii += max_zeros - 1;
+            continue;
+        }
+        part = (sin6->sin6_addr.s6_addr[ii * 2] << 8) | sin6->sin6_addr.s6_addr[ii * 2 + 1];
+        if (part >= 0x1000)
+            APPEND(hexdigits[part >> 12]);
+        if (part >= 0x100)
+            APPEND(hexdigits[(part >> 8) & 15]);
+        if (part >= 0x10)
+            APPEND(hexdigits[(part >> 4) & 15]);
+        APPEND(hexdigits[part & 15]);
+        if (ii < 7)
+            APPEND(':');
+    }
+    APPEND('\0');
+#undef APPEND
+
+    return pos;
+}
+
+static const unsigned char xdigit_value[256] = {
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
+   0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+   0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static unsigned int
+ipv6_pton(struct sockaddr *sa, UNUSED_ARG(unsigned int socklen), unsigned int *bits, const char *input)
+{
+    const char *part_start = NULL;
+    struct sockaddr_in6 *sin6;
+    char *colon;
+    char *dot;
+    unsigned int part = 0, pos = 0, ii = 0, cpos = 8;
+
+    if (!(colon = strchr(input, ':')))
+        return 0;
+    dot = strchr(input, '.');
+    if (dot && dot < colon)
+        return 0;
+    sin6 = (struct sockaddr_in6*)sa;
+    /* Parse IPv6, possibly like ::127.0.0.1.
+     * This is pretty straightforward; the only trick is borrowed
+     * from Paul Vixie (BIND): when it sees a "::" continue as if
+     * it were a single ":", but note where it happened, and fill
+     * with zeros afterwards.
+     */
+    if (input[pos] == ':') {
+        if ((input[pos+1] != ':') || (input[pos+2] == ':'))
+            return 0;
+        cpos = 0;
+        pos += 2;
+        part_start = input + pos;
+    }
+    while (ii < 8) {
+        if (isxdigit(input[pos])) {
+            part = (part << 4) | xdigit_value[(unsigned char)input[pos]];
+            if (part > 0xffff)
+                return 0;
+            pos++;
+        } else if (input[pos] == ':') {
+            part_start = input + ++pos;
+            if (input[pos] == '.')
+                return 0;
+            sin6->sin6_addr.s6_addr[ii * 2] = part >> 8;
+            sin6->sin6_addr.s6_addr[ii * 2 + 1] = part & 255;
+            ii++;
+            part = 0;
+            if (input[pos] == ':') {
+                if (cpos < 8)
+                    return 0;
+                cpos = ii;
+                pos++;
+            }
+        } else if (input[pos] == '.') {
+            uint32_t ip4;
+            unsigned int len;
+            len = sar_pton_ip4(part_start, bits, &ip4);
+            if (!len || (ii > 6))
+                return 0;
+            memcpy(sin6->sin6_addr.s6_addr + ii * 2, &ip4, sizeof(ip4));
+            if (bits)
+                *bits += ii * 16;
+            ii += 2;
+            pos = part_start + len - input;
+            break;
+        } else if (bits && input[pos] == '/' && isdigit(input[pos + 1])) {
+            unsigned int len;
+            char *term;
+
+            len = strtoul(input + pos + 1, &term, 10);
+            if (term <= input + pos + 1)
+                break;
+            else if (len > 128)
+                return 0;
+            if (bits)
+                *bits = len;
+            pos = term - input;
+            break;
+        } else if (cpos <= 8) {
+            sin6->sin6_addr.s6_addr[ii * 2] = part >> 8;
+            sin6->sin6_addr.s6_addr[ii * 2 + 1] = part & 255;
+            ii++;
+            break;
+        } else return 0;
+    }
+    /* Shift stuff after "::" up and fill middle with zeros. */
+    if (cpos < 8) {
+        unsigned int jj;
+        ii <<= 1;
+        cpos <<= 1;
+        for (jj = 0; jj < ii - cpos; jj++)
+            sin6->sin6_addr.s6_addr[15 - jj] = sin6->sin6_addr.s6_addr[ii - jj - 1];
+        for (jj = 0; jj < 16 - ii; jj++)
+            sin6->sin6_addr.s6_addr[cpos + jj] = 0;
+    }
+    sa->sa_family = AF_INET6;
+    return pos;
+}
+
+static int
+ipv6_get_port(const struct sockaddr *sa, UNUSED_ARG(unsigned int socklen))
+{
+    return ntohs(((const struct sockaddr_in6*)sa)->sin6_port);
+}
+
+static int
+ipv6_set_port(struct sockaddr *sa, UNUSED_ARG(unsigned int socklen), unsigned short port)
+{
+    ((struct sockaddr_in6*)sa)->sin6_port = htons(port);
+    return 0;
+}
+
+static unsigned int
+ipv6_addr_request(struct sar_request *req, const char *node, const char *srv_node, unsigned int flags)
+{
+    unsigned int len;
+    if (flags & SAI_V4MAPPED) {
+        if (srv_node)
+            len = sar_request_build(req, node, REQ_TYPE_AAAA, node, REQ_TYPE_A, srv_node, REQ_TYPE_SRV, NULL);
+        else
+            len = sar_request_build(req, node, REQ_TYPE_AAAA, node, REQ_TYPE_A, NULL);
+    } else {
+        if (srv_node)
+            len = sar_request_build(req, node, REQ_TYPE_AAAA, srv_node, REQ_TYPE_SRV, NULL);
+        else
+            len = sar_request_build(req, node, REQ_TYPE_AAAA, NULL);
+    }
+    return len;
+}
+
+static void
+ipv6_ptr_name(struct sar_getname_state *state, const struct sockaddr *sa, UNUSED_ARG(unsigned int socklen))
+{
+    const uint8_t *bytes;
+    unsigned int ii, jj;
+
+    bytes = ((struct sockaddr_in6*)sa)->sin6_addr.s6_addr;
+    memcpy(state->original, bytes, 16);
+    for (jj = 0, ii = 16; ii > 0; ) {
+        state->name[jj++] = hexdigits[bytes[--ii] & 15];
+        state->name[jj++] = hexdigits[bytes[ii] >> 4];
+        state->name[jj++] = '.';
+    }
+    strcpy(state->name + jj, ".ip6.arpa");
+    state->doing_arpa = 1;
+}
+
+static int
+ipv6_decode(struct sar_getaddr_state *state, struct dns_rr *rr, unsigned char *raw, UNUSED_ARG(unsigned int raw_size))
+{
+    struct sockaddr_in6 *sa;
+    struct addrinfo *ai;
+
+    if (state->flags & SAI_CANONNAME) {
+        ai = calloc(1, sizeof(*ai) + sizeof(*sa) + strlen(rr->name) + 1);
+        sa = (struct sockaddr_in6*)(ai->ai_addr = (struct sockaddr*)(ai + 1));
+        ai->ai_canonname = strcpy((char*)(sa + 1), rr->name);
+    } else {
+        ai = calloc(1, sizeof(*ai) + sizeof(*sa));
+        sa = (struct sockaddr_in6*)(ai->ai_addr = (struct sockaddr*)(ai + 1));
+        ai->ai_canonname = NULL;
+    }
+
+    if (rr->rdlength == 4) {
+        sa->sin6_addr.s6_addr[10] = sa->sin6_addr.s6_addr[11] = 0xff;
+        memcpy(sa->sin6_addr.s6_addr + 12, raw + rr->rd_start, 4);
+    } else if (rr->rdlength == 16) {
+        memcpy(sa->sin6_addr.s6_addr, raw + rr->rd_start, 16);
+    } else {
+        free(ai);
+        return 0;
+    }
+
+    ai->ai_family = AF_INET6;
+    sa->sin6_port = htons(state->port);
+    return sar_getaddr_append(state, ai, 1);
+}
+
+static struct sar_family_helper sar_ipv6_helper = {
+    "::1",
+    "::",
+    sizeof(struct sockaddr_in6),
+    AF_INET6,
+    ipv6_ntop,
+    ipv6_pton,
+    ipv6_get_port,
+    ipv6_set_port,
+    ipv6_addr_request,
+    ipv6_ptr_name,
+    ipv6_decode,
+    NULL
+};
+
+#endif /* defined(AF_INET6) */
+
+static void
+sar_cleanup(void)
+{
+    ioset_close(sar_fd, 1);
+    dict_delete(services_byname);
+    dict_delete(services_byport);
+    dict_delete(sar_nameservers);
+    dict_delete(sar_requests);
+}
+
+static void
+sar_conf_reload(void)
+{
+    dict_t node;
+    const char *resolv_conf = "/etc/resolv.conf";
+    const char *services = "/etc/services";
+    const char *str;
+
+    node = conf_get_data("modules/sar", RECDB_OBJECT);
+    if (node != NULL) {
+        str = database_get_data(node, "resolv_conf", RECDB_QSTRING);
+        if (str) resolv_conf = str;
+        str = database_get_data(node, "services", RECDB_QSTRING);
+        if (str) services = str;
+    }
+    sar_dns_init(resolv_conf);
+    sar_services_init(services);
+}
+
+void
+sar_init(void)
+{
+    reg_exit_func(sar_cleanup);
+    sar_log = log_register_type("sar", NULL);
+
+    sar_requests = dict_new();
+    dict_set_free_data(sar_requests, sar_request_cleanup);
+
+    sar_nameservers = dict_new();
+    dict_set_free_data(sar_nameservers, free);
+
+    services_byname = dict_new();
+    dict_set_free_data(services_byname, free);
+
+    services_byport = dict_new();
+    dict_set_free_data(services_byport, free);
+
+    sar_register_helper(&sar_ipv4_helper);
+#if defined(AF_INET6)
+    sar_register_helper(&sar_ipv6_helper);
+#endif
+
+    conf_register_reload(sar_conf_reload);
+}
diff --git a/src/sar.h b/src/sar.h
new file mode 100644 (file)
index 0000000..9616fc1
--- /dev/null
+++ b/src/sar.h
@@ -0,0 +1,158 @@
+/* sar.h - srvx asynchronous resolver
+ * Copyright 2005, 2007 Michael Poole <mdpoole@troilus.org>
+ *
+ * This file is part of srvx.
+ *
+ * srvx is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with srvx; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+
+#if !defined(SRVX_SAR_H)
+#define SRVX_SAR_H
+
+#include "common.h"
+
+#define SAI_NUMERICHOST 0x01 /* simply translate address from text form */
+#define SAI_CANONNAME   0x02 /* fill in canonical name of host */
+#define SAI_PASSIVE     0x04 /* if node==NULL, use unspecified address */
+#define SAI_V4MAPPED    0x08 /* accept v4-mapped IPv6 addresses */
+#define SAI_ALL         0x10 /* return both IPv4 and IPv6 addresses */
+#define SAI_NOSRV       0x20 /* suppress SRV even if default is to use it */
+#define SAI_FORCESRV    0x40 /* force SRV request even if questionable */
+
+#define SNI_NOFQDN      0x01 /* omit domain name for local hosts */
+#define SNI_NUMERICHOST 0x02 /* do not resolve address, just translate to text */
+#define SNI_NAMEREQD    0x04 /* indicate error if no name exists */
+#define SNI_NUMERICSERV 0x08 /* return service in numeric form */
+#define SNI_DGRAM       0x10 /* return service names for UDP use */
+#define SNI_PARANOID    0x20 /* confirm forward resolution of name */
+
+enum sar_errcode {
+    SAI_SUCCESS,
+    SAI_FAMILY,
+    SAI_SOCKTYPE,
+    SAI_BADFLAGS,
+    SAI_NONAME,
+    SAI_SERVICE,
+    SAI_ADDRFAMILY,
+    SAI_NODATA,
+    SAI_MEMORY,
+    SAI_FAIL,
+    SAI_AGAIN,
+    SAI_MISMATCH,
+    SAI_SYSTEM
+};
+
+struct sockaddr;
+struct addrinfo;
+struct sar_request;
+
+void sar_init(void);
+const char *sar_strerror(enum sar_errcode errcode);
+
+int sar_get_port(const struct sockaddr *sa, unsigned int socklen);
+int sar_set_port(struct sockaddr *sa, unsigned int socklen, unsigned short port);
+unsigned int sar_pton(struct sockaddr *sa, unsigned int socklen, unsigned int *bits, const char *input);
+typedef void (*sar_addr_cb)(void *ctx, struct addrinfo *res, enum sar_errcode errcode);
+struct sar_request *sar_getaddr(const char *node, const char *service, const struct addrinfo *hints, sar_addr_cb cb, void *cb_ctx);
+void sar_free(struct addrinfo *ai);
+
+/** Maximum value returnable by sar_ntop(). */
+#define SAR_NTOP_MAX 40
+unsigned int sar_ntop(char *output, unsigned int out_size, const struct sockaddr *sa, unsigned int socklen);
+typedef void (*sar_name_cb)(void *ctx, const char *host, const char *serv, enum sar_errcode errcode);
+struct sar_request *sar_getname(const struct sockaddr *sa, unsigned int salen, int flags, sar_name_cb cb, void *cb_ctx);
+
+/** Generic DNS lookup support. */
+
+/** DNS message (request and response) header. */
+struct dns_header {
+    uint16_t id;
+    uint16_t flags;
+#define REQ_FLAG_QR           0x8000 /* response */
+#define REQ_FLAG_OPCODE_MASK  0x7800 /* opcode mask */
+#define REQ_FLAG_OPCODE_SHIFT 11     /* opcode shift count */
+#define REQ_OPCODE_QUERY      (0 << REQ_FLAG_OPCODE_SHIFT)
+#define REQ_FLAG_AA           0x0400 /* authoritative answer */
+#define REQ_FLAG_TC           0x0200 /* truncated message */
+#define REQ_FLAG_RD           0x0100 /* recursion desired */
+#define REQ_FLAG_RA           0x0080 /* recursion available */
+/* 0x0040 bit currently reserved and must be zero; 0x0020 and 0x0010
+ * used by DNSSEC. */
+#define REQ_FLAG_RCODE_MASK   0x000f /* response code mask */
+#define REQ_FLAG_RCODE_SHIFT  0      /* response code shift count */
+#define RCODE_NO_ERROR        0
+#define RCODE_FORMAT_ERROR    1
+#define RCODE_SERVER_FAILURE  2
+#define RCODE_NAME_ERROR      3  /* aka NXDOMAIN (since RFC2308) */
+#define RCODE_NOT_IMPLEMENTED 4
+#define RCODE_REFUSED         5
+#define RCODE_BAD_OPT_VERSION 16 /* RFC 2671 */
+    uint16_t qdcount;  /* count of questions */
+    uint16_t ancount;  /* count of answer RRs */
+    uint16_t nscount;  /* count of NS (authority) RRs */
+    uint16_t arcount;  /* count of additional RRs */
+};
+
+/** DNS resource record. */
+struct dns_rr {
+    uint16_t type;
+    uint16_t class;
+    uint32_t ttl;
+    uint16_t rdlength;
+    uint16_t rd_start;
+    char *name;
+};
+
+#define REQ_TYPE_A     1
+#define REQ_TYPE_NS    2
+#define REQ_TYPE_CNAME 5
+#define REQ_TYPE_SOA   6
+#define REQ_TYPE_PTR   12
+#define REQ_TYPE_MX    15
+#define REQ_TYPE_TXT   16
+#define REQ_TYPE_AAAA  28  /* RFC 3596 */
+#define REQ_TYPE_SRV   33  /* RFC 2782 */
+#define REQ_TYPE_OPT   41  /* RFC 2671 */
+#define REQ_QTYPE_ALL  255
+#define REQ_CLASS_IN   1
+#define REQ_QCLASS_ALL 255
+
+struct sar_request;
+typedef void (*sar_request_ok_cb)(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, unsigned char *raw, unsigned int raw_size);
+typedef void (*sar_request_fail_cb)(struct sar_request *req, unsigned int rcode);
+
+/** Pending request structure.
+ * User code should treat this structure as opaque.
+ */
+struct sar_request {
+    int id;
+    time_t expiry;
+    sar_request_ok_cb cb_ok;
+    sar_request_fail_cb cb_fail;
+    unsigned char *body;
+    unsigned int body_len;
+    unsigned char retries;
+    char id_text[6];
+};
+
+const char *sar_rcode_text(unsigned int rcode);
+struct sar_request *sar_request_alloc(unsigned int data_len, sar_request_ok_cb ok_cb, sar_request_fail_cb fail_cb);
+unsigned int sar_request_build(struct sar_request *req, ...);
+void sar_request_send(struct sar_request *req);
+struct sar_request *sar_request_simple(unsigned int data_len, sar_request_ok_cb ok_cb, sar_request_fail_cb fail_cb, ...);
+void sar_request_abort(struct sar_request *req);
+char *sar_extract_name(const unsigned char *buf, unsigned int size, unsigned int *ppos);
+
+#endif /* !defined(SRVX_SAR_H) */
index 0e112fc53b1ad715b286325dd53c93c7840946ab..8abac6f4d8f87fb7bc82f3cad2c67e1605e07bdf 100644 (file)
         "file_reason" "client is blacklisted";
         // How long should a blacklist G-line last?
         "gline_duration" "1h";
+        // If you want to use DNS blacklists, add them here:
+        "dnsbl" {
+            // This DNSBL zone does not exist - you'll have to pick your own.
+            "dnsbl.example.org" {
+                "description" "Example DNSBL entry";
+                "reason" "busted by a dns blacklist";
+                "duration" "1h";
+                // You can stick the client's IP in the G-line message.
+                "reason_2" "Example DNSBL reported %ip%'s address as 127.0.0.2";
+                // .. or the contents of a DNS TXT.
+                "reason_3" "%txt%";
+            };
+        };
+    };
+    "sar" {
+        // You generally will not want to override these defaults.
+        // "resolv_conf" "/etc/resolv.conf";
+        // "services" "/etc/services";
+        // "bind_address" "0.0.0.0";
+        // "bind_port" "0";
+        // The defaults for these are derived from the system config files (above).
+        // "domain" "example.org";
+        // "timeout" "3"; // base timeout for a DNS reply
+        // "retries" "3"; // number of times to retry on different servers or longer timeouts
+        // "ndots" "1";   // number of dots needed in a hostname to bypass search path
+        // "edns0" "0";   // if set, enable EDNS0 extended message sizes
+        // "search" ("example.org", "example.net");
+        // "nameservers" ("127.0.0.1");
     };
 };