From 133752aaace085ed30baf4e5e43bcb313d778b17 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Fri, 10 Jun 2011 14:02:06 +0200
Subject: [PATCH 3/4] Resolve hosts by name from DNS into resolv_hostent

---
 src/resolv/async_resolv.c |  256 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 256 insertions(+), 0 deletions(-)

diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c
index 6832b98fe063618e35959590d241a28c8da8484d..9d84496a076fb53d7d3dc6b05ac54d7f6510e7f4 100644
--- a/src/resolv/async_resolv.c
+++ b/src/resolv/async_resolv.c
@@ -53,6 +53,9 @@
     _ares_malloc_data(data)
 #endif /* HAVE_ARES_DATA */
 
+#define DNS__16BIT(p)                   (((p)[0] << 8) | (p)[1])
+#define DNS_HEADER_ANCOUNT(h)           DNS__16BIT((h) + 6)
+
 struct fd_watch {
     struct fd_watch *prev;
     struct fd_watch *next;
@@ -784,6 +787,259 @@ resolv_gethostbyname_files_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
     return EOK;
 }
 
+/* ==================== Resolve host name in DNS =========================*/
+struct gethostbyname_dns_state {
+    struct resolv_ctx *resolv_ctx;
+    struct tevent_context *ev;
+
+    /* Part of the query. */
+    const char *name;
+    int family;
+
+    /* query result */
+    struct resolv_hostent *rhostent;
+
+    /* These are returned by ares. */
+    int status;
+    int timeouts;
+    int retrying;
+};
+
+static void
+resolv_gethostbyname_dns_wakeup(struct tevent_req *subreq);
+static void
+resolv_gethostbyname_dns_query(struct tevent_req *req,
+                               struct gethostbyname_dns_state *state);
+static void
+resolv_gethostbyname_dns_query_done(void *arg, int status, int timeouts,
+                                    unsigned char *abuf, int alen);
+static int
+resolv_gethostbyname_dns_parse(struct gethostbyname_dns_state *state, int status,
+                               int timeouts, unsigned char *abuf, int alen);
+
+static struct tevent_req *
+resolv_gethostbyname_dns_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+                              struct resolv_ctx *ctx, const char *name,
+                              int family)
+{
+    struct tevent_req *req, *subreq;
+    struct gethostbyname_dns_state *state;
+    struct timeval tv = { 0, 0 };
+
+    if (ctx->channel == NULL) {
+        DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+        return NULL;
+    }
+
+    req = tevent_req_create(mem_ctx, &state, struct gethostbyname_dns_state);
+    if (req == NULL) {
+        return NULL;
+    }
+
+    state->resolv_ctx = ctx;
+    state->ev = ev;
+    state->name = name;
+    state->rhostent = NULL;
+    state->status = 0;
+    state->timeouts = 0;
+    state->retrying = 0;
+    state->family = family;
+
+    /* We need to have a wrapper around ares async calls, because
+     * they can in some cases call it's callback immediately.
+     * This would not let our caller to set a callback for req. */
+    subreq = tevent_wakeup_send(req, ev, tv);
+    if (subreq == NULL) {
+        DEBUG(1, ("Failed to add critical timer to run next operation!\n"));
+        talloc_zfree(req);
+        return NULL;
+    }
+    tevent_req_set_callback(subreq, resolv_gethostbyname_dns_wakeup, req);
+    schedule_timeout_watcher(ev, ctx);
+
+    return req;
+}
+
+static void
+resolv_gethostbyname_dns_wakeup(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                struct tevent_req);
+    struct gethostbyname_dns_state *state = tevent_req_data(req,
+                                        struct gethostbyname_dns_state);
+
+    if (!tevent_wakeup_recv(subreq)) {
+        tevent_req_error(req, EIO);
+        return;
+    }
+    talloc_zfree(subreq);
+
+    if (state->resolv_ctx->channel == NULL) {
+        DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+        tevent_req_error(req, EIO);
+        return;
+    }
+
+    resolv_gethostbyname_dns_query(req, state);
+}
+
+static void
+resolv_gethostbyname_dns_query(struct tevent_req *req,
+                               struct gethostbyname_dns_state *state)
+{
+    DEBUG(4, ("Trying to resolve %s record of '%s' in DNS\n",
+              state->family == AF_INET ? "A" : "AAAA", state->name));
+
+    ares_query(state->resolv_ctx->channel,
+               state->name, ns_c_in,
+               (state->family == AF_INET) ? ns_t_a : ns_t_aaaa,
+               resolv_gethostbyname_dns_query_done, req);
+}
+
+static void
+resolv_gethostbyname_dns_query_done(void *arg, int status, int timeouts,
+                                    unsigned char *abuf, int alen)
+{
+    struct tevent_req *req = talloc_get_type(arg, struct tevent_req);
+    struct gethostbyname_dns_state *state = tevent_req_data(req,
+                                        struct gethostbyname_dns_state);
+    errno_t ret;
+
+    unschedule_timeout_watcher(state->resolv_ctx);
+
+    state->status = status;
+    state->timeouts = timeouts;
+
+    /* If resolv.conf changed during processing of a request we might
+     * destroy the old channel before the request has a chance to finish.
+     * We must resend the request in this case */
+    if (state->retrying == 0 && status == ARES_EDESTRUCTION
+        && state->resolv_ctx->channel != NULL) {
+        state->retrying = 1;
+        schedule_timeout_watcher(state->ev, state->resolv_ctx);
+        resolv_gethostbyname_dns_query(req, state);
+        return;
+    }
+
+    if (status == ARES_ENOTFOUND || status == ARES_ENODATA) {
+        /* Just say we didn't find anything and let the caller decide
+         * about retrying */
+        tevent_req_error(req, ENOENT);
+        return;
+    }
+
+    if (status != ARES_SUCCESS) {
+        /* Any other error indicates a server error,
+         * so don't bother trying again
+         */
+        tevent_req_error(req, return_code(status));
+        return;
+    }
+
+    ret = resolv_gethostbyname_dns_parse(state, status, timeouts, abuf, alen);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    tevent_req_done(req);
+}
+
+static int
+resolv_gethostbyname_dns_parse(struct gethostbyname_dns_state *state,
+                               int status, int timeouts,
+                               unsigned char *abuf, int alen)
+{
+    TALLOC_CTX *tmp_ctx;
+    struct hostent *hostent;
+    struct ares_addrttl *addrttls = NULL;
+    struct ares_addr6ttl *addr6ttls = NULL;
+    void *addr;
+    int naddrttls;
+    errno_t ret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    naddrttls = DNS_HEADER_ANCOUNT(abuf);
+
+    switch (state->family) {
+        case AF_INET:
+            DEBUG(7, ("Parsing an A reply\n"));
+
+            addrttls = talloc_array(state, struct ares_addrttl, naddrttls);
+            if (!addrttls) {
+                ret = ENOMEM;
+                goto fail;
+            }
+            addr = addrttls;
+
+            status = ares_parse_a_reply(abuf, alen, &hostent,
+                                        addrttls, &naddrttls);
+            break;
+        case AF_INET6:
+            DEBUG(7, ("Parsing an AAAA reply\n"));
+
+            addr6ttls = talloc_array(state, struct ares_addr6ttl, naddrttls);
+            if (!addr6ttls) {
+                ret = ENOMEM;
+                goto fail;
+            }
+            addr = addr6ttls;
+
+            status = ares_parse_aaaa_reply(abuf, alen, &hostent,
+                                           addr6ttls, &naddrttls);
+            break;
+        default:
+            DEBUG(1, ("Unknown family %d\n", state->family));
+            ret = EAFNOSUPPORT;
+            goto fail;
+    }
+
+    if (hostent != NULL) {
+        state->rhostent = resolv_copy_hostent_ares(state, hostent,
+                                                   state->family,
+                                                   addr, naddrttls);
+        free(hostent);
+        if (state->rhostent == NULL) {
+            ret = ENOMEM;
+            goto fail;
+        }
+    }
+
+    talloc_free(tmp_ctx);
+    return return_code(status);
+
+fail:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static int
+resolv_gethostbyname_dns_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+                              int *status, int *timeouts,
+                              struct resolv_hostent **rhostent)
+{
+    struct gethostbyname_dns_state *state = tevent_req_data(req,
+                                        struct gethostbyname_dns_state);
+
+    /* Fill in even in case of error as status contains the
+     * c-ares return code */
+    if (status) {
+        *status = state->status;
+    }
+    if (timeouts) {
+        *timeouts = state->timeouts;
+    }
+    if (rhostent) {
+        *rhostent = talloc_steal(mem_ctx, state->rhostent);
+    }
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    return EOK;
+}
+
 /*******************************************************************
  * Get host by name.                                               *
  *******************************************************************/
-- 
1.7.5.2

