s4-resolve: Remove dependency on libroken
[nivanova/samba-autobuild/.git] / source4 / libcli / resolve / dns_ex.c
index 36d7269be4956e2819623469cf7c5149491c4061..3b303d261f9853b3ab1b53cb2e070d703616a974 100644 (file)
@@ -5,6 +5,7 @@
 
    Copyright (C) Andrew Tridgell 2005
    Copyright (C) Stefan Metzmacher 2008
+   Copyright (C) Matthieu Patou 2011
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #include "libcli/composite/composite.h"
 #include "librpc/gen_ndr/ndr_nbt.h"
 #include "libcli/resolve/resolve.h"
-#include "heimdal/lib/roken/resolve.h"
+#include "lib/util/util_net.h"
+#include "lib/addns/dnsquery.h"
+#include "lib/addns/dns.h"
+#include <arpa/nameser.h>
+#include <resolv.h>
 
 struct dns_ex_state {
-       bool do_getaddrinfo;
        bool do_fallback;
-       bool do_srv;
        uint32_t flags;
+       uint16_t port;
        struct nbt_name name;
        struct socket_address **addrs;
+       char **names;
        pid_t child;
        int child_fd;
-       struct fd_event *fde;
-       struct event_context *event_ctx;
+       struct tevent_fd *fde;
+       struct tevent_context *event_ctx;
 };
 
 /*
@@ -62,7 +67,6 @@ static int dns_ex_destructor(struct dns_ex_state *state)
        int status;
 
        kill(state->child, SIGTERM);
-       close(state->child_fd);
        if (waitpid(state->child, &status, WNOHANG) == 0) {
                kill(state->child, SIGKILL);
                waitpid(state->child, &status, 0);
@@ -71,154 +75,282 @@ static int dns_ex_destructor(struct dns_ex_state *state)
        return 0;
 }
 
-/*
-  the blocking child
-*/
-static void run_child_dns_lookup(struct dns_ex_state *state, int fd)
+struct dns_records_container {
+       char **list;
+       uint32_t count;
+};
+
+static int reply_to_addrs(TALLOC_CTX *mem_ctx, uint32_t *a_num,
+                         char ***cur_addrs, uint32_t total,
+                         struct dns_request *reply, int port)
 {
-       struct dns_reply *reply;
-       struct resource_record *rr;
-       uint32_t count = 0;
-       uint32_t srv_valid = 0;
-       struct resource_record **srv_rr;
-       uint32_t addrs_valid = 0;
-       struct resource_record **addrs_rr;
-       char *addrs;
-       bool first;
+       char addrstr[INET6_ADDRSTRLEN];
+       struct dns_rrec *rr;
+       char **addrs;
        uint32_t i;
+       const char *addr;
 
-       /* this is the blocking call we are going to lots of trouble
-          to avoid in the parent */
-       reply = dns_lookup(state->name.name, state->do_srv?"SRV":"A");
-       if (!reply) {
-               goto done;
+       /* at most we over-allocate here, but not by much */
+       addrs = talloc_realloc(mem_ctx, *cur_addrs, char *,
+                               total + reply->num_answers);
+       if (!addrs) {
+               return 0;
        }
+       *cur_addrs = addrs;
 
-       if (state->do_srv) {
-               dns_srv_order(reply);
-       }
+       for (i = 0; reply->answers[i]; i++) {
+               rr = reply->answers[i];
 
-       /* Loop over all returned records and pick the "srv" records */
-       for (rr=reply->head; rr; rr=rr->next) {
                /* we are only interested in the IN class */
-               if (rr->class != C_IN) {
+               if (rr->r_class != DNS_CLASS_IN) {
                        continue;
                }
 
-               if (state->do_srv) {
-                       /* we are only interested in SRV records */
-                       if (rr->type != T_SRV) {
-                               continue;
-                       }
+               if (rr->type == QTYPE_NS) {
+                       /*
+                        * After the record for NS will come the A or AAAA
+                        * record of the NS.
+                        */
+                       break;
+               }
 
-                       /* verify we actually have a SRV record here */
-                       if (!rr->u.srv) {
-                               continue;
-                       }
+               /* verify we actually have a record here */
+               if (!rr->data) {
+                       continue;
+               }
 
-                       /* Verify we got a port */
-                       if (rr->u.srv->port == 0) {
+               /* we are only interested in A and AAAA records */
+               switch (rr->type) {
+               case QTYPE_A:
+                       addr = inet_ntop(AF_INET,
+                                        (struct in_addr *)rr->data,
+                                        addrstr, sizeof(addrstr));
+                       if (addr == NULL) {
                                continue;
                        }
-               } else {
-                       /* we are only interested in A records */
-                       /* TODO: add AAAA support */
-                       if (rr->type != T_A) {
+                       break;
+               case QTYPE_AAAA:
+#ifdef HAVE_IPV6
+                       addr = inet_ntop(AF_INET6,
+                                        (struct in6_addr *)rr->data,
+                                        addrstr, sizeof(addrstr));
+#else
+                       addr = NULL;
+#endif
+                       if (addr == NULL) {
                                continue;
                        }
+               default:
+                       continue;
+               }
 
-                       /* verify we actually have a A record here */
-                       if (!rr->u.a) {
-                               continue;
+               addrs[total] = talloc_asprintf(addrs, "%s@%u/%s",
+                                               addrstr, port,
+                                               rr->name->pLabelList->label);
+               if (addrs[total]) {
+                       total++;
+                       if (rr->type == QTYPE_A) {
+                               (*a_num)++;
                        }
                }
-               count++;
        }
 
-       if (count == 0) {
-               goto done;
-       }
+       return total;
+}
 
-       srv_rr = talloc_zero_array(state,
-                                  struct resource_record *,
-                                  count);
-       if (!srv_rr) {
-               goto done;
+static DNS_ERROR dns_lookup(TALLOC_CTX *mem_ctx, const char* name,
+                           uint16_t q_type, struct dns_request **reply)
+{
+       int len, rlen;
+       uint8_t *answer;
+       bool loop;
+       struct dns_buffer buf;
+       DNS_ERROR err;
+
+       /* give space for a good sized answer by default */
+       answer = NULL;
+       len = 1500;
+       do {
+               answer = talloc_realloc(mem_ctx, answer, uint8_t, len);
+               if (!answer) {
+                       return ERROR_DNS_NO_MEMORY;
+               }
+               rlen = res_search(name, DNS_CLASS_IN, q_type, answer, len);
+               if (rlen == -1) {
+                       if (len >= 65535) {
+                               return ERROR_DNS_SOCKET_ERROR;
+                       }
+                       /* retry once with max packet size */
+                       len = 65535;
+                       loop = true;
+               } else if (rlen > len) {
+                       len = rlen;
+                       loop = true;
+               } else {
+                       loop = false;
+               }
+       } while(loop);
+
+       buf.data = answer;
+       buf.size = rlen;
+       buf.offset = 0;
+       buf.error = ERROR_DNS_SUCCESS;
+
+       err = dns_unmarshall_request(mem_ctx, &buf, reply);
+
+       TALLOC_FREE(answer);
+       return err;
+}
+
+static struct dns_records_container get_a_aaaa_records(TALLOC_CTX *mem_ctx,
+                                                       const char* name,
+                                                       int port)
+{
+       struct dns_request *reply;
+       struct dns_records_container ret;
+       char **addrs = NULL;
+       uint32_t a_num, total;
+       uint16_t qtype;
+       TALLOC_CTX *tmp_ctx;
+       DNS_ERROR err;
+
+       memset(&ret, 0, sizeof(struct dns_records_container));
+
+       tmp_ctx = talloc_new(mem_ctx);
+       if (!tmp_ctx) {
+               return ret;
        }
 
-       addrs_rr = talloc_zero_array(state,
-                                    struct resource_record *,
-                                    count);
-       if (!addrs_rr) {
-               goto done;
+       qtype = QTYPE_AAAA;
+
+       /* this is the blocking call we are going to lots of trouble
+          to avoid them in the parent */
+       err = dns_lookup(tmp_ctx, name, qtype, &reply);
+       if (!ERR_DNS_IS_OK(err)) {
+               qtype = QTYPE_A;
+               err = dns_lookup(tmp_ctx, name, qtype, &reply);
+               if (!ERR_DNS_IS_OK(err)) {
+                       goto done;
+               }
        }
 
-       /* Loop over all returned records and pick the records */
-       for (rr=reply->head;rr;rr=rr->next) {
-               /* we are only interested in the IN class */
-               if (rr->class != C_IN) {
-                       continue;
+       a_num = total = 0;
+       total = reply_to_addrs(tmp_ctx, &a_num, &addrs, total, reply, port);
+
+       if (qtype == QTYPE_AAAA && a_num == 0) {
+               /*
+               * DNS server didn't returned A when asked for AAAA records.
+               * Most of the server do it, let's ask for A specificaly.
+               */
+               err = dns_lookup(tmp_ctx, name, QTYPE_A, &reply);
+               if (!ERR_DNS_IS_OK(err)) {
+                       goto done;
                }
 
-               if (state->do_srv) {
-                       /* we are only interested in SRV records */
-                       if (rr->type != T_SRV) {
-                               continue;
-                       }
+               total = reply_to_addrs(tmp_ctx, &a_num, &addrs, total,
+                                       reply, port);
 
-                       /* verify we actually have a srv record here */
-                       if (!rr->u.srv) {
-                               continue;
-                       }
+       }
 
-                       /* Verify we got a port */
-                       if (rr->u.srv->port == 0) {
-                               continue;
-                       }
+       if (total) {
+               talloc_steal(mem_ctx, addrs);
+               ret.count = total;
+               ret.list = addrs;
+       }
 
-                       srv_rr[srv_valid] = rr;
-                       srv_valid++;
-               } else {
-                       /* we are only interested in A records */
-                       /* TODO: add AAAA support */
-                       if (rr->type != T_A) {
-                               continue;
-                       }
+done:
+       TALLOC_FREE(tmp_ctx);
+       return ret;
+}
 
-                       /* verify we actually have a A record here */
-                       if (!rr->u.a) {
-                               continue;
-                       }
+static struct dns_records_container get_srv_records(TALLOC_CTX *mem_ctx,
+                                                       const char* name)
+{
+       struct dns_records_container ret;
+       char **addrs = NULL;
+       struct dns_rr_srv *dclist;
+       NTSTATUS status;
+       uint32_t total;
+       unsigned i;
+       int count;
 
-                       addrs_rr[addrs_valid] = rr;
-                       addrs_valid++;
-               }
+       memset(&ret, 0, sizeof(struct dns_records_container));
+       /* this is the blocking call we are going to lots of trouble
+          to avoid them in the parent */
+       status = ads_dns_lookup_srv(mem_ctx, NULL, name, &dclist, &count);
+       if (!NT_STATUS_IS_OK(status)) {
+               return ret;
+       }
+       total = 0;
+       if (count == 0) {
+               return ret;
        }
 
-       for (i=0; i < srv_valid; i++) {
-               for (rr=reply->head;rr;rr=rr->next) {
+       /* Loop over all returned records and pick the records */
+       for (i = 0; i < count; i++) {
+               struct dns_records_container c;
+               const char* tmp_str;
+
+               tmp_str = dclist[i].hostname;
+               if (strchr(tmp_str, '.') && tmp_str[strlen(tmp_str)-1] != '.') {
+                       /* we are asking for a fully qualified name, but the
+                       name doesn't end in a '.'. We need to prevent the
+                       DNS library trying the search domains configured in
+                       resolv.conf */
+                       tmp_str = talloc_asprintf(mem_ctx, "%s.", tmp_str);
+               }
 
-                       if (rr->class != C_IN) {
-                               continue;
-                       }
+               c = get_a_aaaa_records(mem_ctx, tmp_str, dclist[i].port);
+               total += c.count;
+               if (addrs == NULL) {
+                       addrs = c.list;
+               } else {
+                       unsigned j;
 
-                       /* we are only interested in SRV records */
-                       if (rr->type != T_A) {
-                               continue;
+                       addrs = talloc_realloc(mem_ctx, addrs, char*, total);
+                       for (j=0; j < c.count; j++) {
+                               addrs[total - j - 1] = talloc_steal(addrs, c.list[j]);
                        }
+               }
+       }
 
-                       /* verify we actually have a srv record here */
-                       if (strcmp(&srv_rr[i]->u.srv->target[0], rr->domain) != 0) {
-                               continue;
-                       }
+       if (total) {
+               ret.count = total;
+               ret.list = addrs;
+       }
 
-                       addrs_rr[i] = rr;
-                       addrs_valid++;
-                       break;
-               }
+       return ret;
+}
+/*
+  the blocking child
+*/
+static void run_child_dns_lookup(struct dns_ex_state *state, int fd)
+{
+       bool first;
+       bool do_srv = (state->flags & RESOLVE_NAME_FLAG_DNS_SRV);
+       struct dns_records_container c;
+       char* addrs = NULL;
+       unsigned int i;
+
+       if (strchr(state->name.name, '.') && state->name.name[strlen(state->name.name)-1] != '.') {
+               /* we are asking for a fully qualified name, but the
+                  name doesn't end in a '.'. We need to prevent the
+                  DNS library trying the search domains configured in
+                  resolv.conf */
+               state->name.name = talloc_strdup_append(discard_const_p(char, state->name.name),
+                                                       ".");
        }
 
-       if (addrs_valid == 0) {
+
+       if (do_srv) {
+               c = get_srv_records(state, state->name.name);
+       } else {
+               c = get_a_aaaa_records(state, state->name.name, state->port);
+       }
+
+       /* This line in critical - if we return without writing to the
+        * pipe, this is the signal that the name did not exist */
+       if (c.count == 0) {
                goto done;
        }
 
@@ -227,21 +359,16 @@ static void run_child_dns_lookup(struct dns_ex_state *state, int fd)
                goto done;
        }
        first = true;
-       for (i=0; i < count; i++) {
-               if (!addrs_rr[i]) {
-                       continue;
-               }
-               addrs = talloc_asprintf_append_buffer(addrs, "%s%s:%u",
-                                                     first?"":",",
-                                                     inet_ntoa(*addrs_rr[i]->u.a),
-                                                     srv_rr[i]?srv_rr[i]->u.srv->port:0);
-               if (!addrs) {
-                       goto done;
-               }
+
+       for (i=0; i < c.count; i++) {
+               addrs = talloc_asprintf_append_buffer(addrs, "%s%s",
+                                                       first?"":",",
+                                                       c.list[i]);
                first = false;
        }
 
        if (addrs) {
+               DEBUG(11, ("Addrs = %s\n", addrs));
                write(fd, addrs, talloc_get_size(addrs));
        }
 
@@ -263,14 +390,22 @@ static void run_child_getaddrinfo(struct dns_ex_state *state, int fd)
 
        ZERO_STRUCT(hints);
        hints.ai_socktype = SOCK_STREAM;
-       hints.ai_family = AF_INET;/* TODO: add AF_INET6 support */
        hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
 
        ret = getaddrinfo(state->name.name, "0", &hints, &res_list);
-       if (ret == EAI_NODATA && state->do_fallback) {
-               /* getaddrinfo() doesn't handle CNAME records */
-               run_child_dns_lookup(state, fd);
-               return;
+       /* try to fallback in case of error */
+       if (state->do_fallback) {
+               switch (ret) {
+#ifdef EAI_NODATA
+               case EAI_NODATA:
+#endif
+               case EAI_NONAME:
+                       /* getaddrinfo() doesn't handle CNAME records */
+                       run_child_dns_lookup(state, fd);
+                       return;
+               default:
+                       break;
+               }
        }
        if (ret != 0) {
                goto done;
@@ -282,17 +417,15 @@ static void run_child_getaddrinfo(struct dns_ex_state *state, int fd)
        }
        first = true;
        for (res = res_list; res; res = res->ai_next) {
-               struct sockaddr_in *in;
-
-               if (res->ai_family != AF_INET) {
+               char addrstr[INET6_ADDRSTRLEN];
+               if (!print_sockaddr_len(addrstr, sizeof(addrstr), (struct sockaddr *)res->ai_addr, res->ai_addrlen)) {
                        continue;
                }
-               in = (struct sockaddr_in *)res->ai_addr;
-
-               addrs = talloc_asprintf_append_buffer(addrs, "%s%s:%u",
+               addrs = talloc_asprintf_append_buffer(addrs, "%s%s@%u/%s",
                                                      first?"":",",
-                                                     inet_ntoa(in->sin_addr),
-                                                     0);
+                                                     addrstr,
+                                                     state->port,
+                                                     state->name.name);
                if (!addrs) {
                        goto done;
                }
@@ -312,34 +445,48 @@ done:
 /*
   handle a read event on the pipe
 */
-static void pipe_handler(struct event_context *ev, struct fd_event *fde, 
+static void pipe_handler(struct tevent_context *ev, struct tevent_fd *fde, 
                         uint16_t flags, void *private_data)
 {
        struct composite_context *c = talloc_get_type(private_data, struct composite_context);
        struct dns_ex_state *state = talloc_get_type(c->private_data,
                                     struct dns_ex_state);
-       char address[2048];
+       char *address;
        uint32_t num_addrs, i;
        char **addrs;
        int ret;
        int status;
+       int value = 0;
 
        /* if we get any event from the child then we know that we
           won't need to kill it off */
        talloc_set_destructor(state, NULL);
 
-       /* yes, we don't care about EAGAIN or other niceities
-          here. They just can't happen with this parent/child
-          relationship, and even if they did then giving an error is
-          the right thing to do */
-       ret = read(state->child_fd, address, sizeof(address)-1);
-       close(state->child_fd);
+       if (ioctl(state->child_fd, FIONREAD, &value) != 0) {
+               value = 8192;
+       }
+
+       address = talloc_array(state, char, value+1);
+       if (address) {
+               /* yes, we don't care about EAGAIN or other niceities
+                  here. They just can't happen with this parent/child
+                  relationship, and even if they did then giving an error is
+                  the right thing to do */
+               ret = read(state->child_fd, address, value);
+       } else {
+               ret = -1;
+       }
        if (waitpid(state->child, &status, WNOHANG) == 0) {
                kill(state->child, SIGKILL);
                waitpid(state->child, &status, 0);
        }
 
        if (ret <= 0) {
+               /* The check for ret == 0 here is important, if the
+                * name does not exist, then no bytes are written to
+                * the pipe */
+               DEBUG(3,("dns child failed to find name '%s' of type %s\n",
+                        state->name.name, (state->flags & RESOLVE_NAME_FLAG_DNS_SRV)?"SRV":"A"));
                composite_error(c, NT_STATUS_OBJECT_NAME_NOT_FOUND);
                return;
        }
@@ -356,9 +503,13 @@ static void pipe_handler(struct event_context *ev, struct fd_event *fde,
                                    num_addrs+1);
        if (composite_nomem(state->addrs, c)) return;
 
+       state->names = talloc_array(state, char *, num_addrs+1);
+       if (composite_nomem(state->names, c)) return;
+
        for (i=0; i < num_addrs; i++) {
                uint32_t port = 0;
-               char *p = strrchr(addrs[i], ':');
+               char *p = strrchr(addrs[i], '@');
+               char *n;
 
                if (!p) {
                        composite_error(c, NT_STATUS_OBJECT_NAME_NOT_FOUND);
@@ -368,22 +519,35 @@ static void pipe_handler(struct event_context *ev, struct fd_event *fde,
                *p = '\0';
                p++;
 
-               if (strcmp(addrs[i], "0.0.0.0") == 0 ||
-                   inet_addr(addrs[i]) == INADDR_NONE) {
+               n = strrchr(p, '/');
+               if (!n) {
+                       composite_error(c, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+                       return;
+               }
+
+               *n = '\0';
+               n++;
+
+               if (strcmp(addrs[i], "0.0.0.0") == 0) {
                        composite_error(c, NT_STATUS_OBJECT_NAME_NOT_FOUND);
                        return;
                }
                port = strtoul(p, NULL, 10);
                if (port > UINT16_MAX) {
-                       port = 0;
+                       composite_error(c, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+                       return;
                }
                state->addrs[i] = socket_address_from_strings(state->addrs,
-                                                             "ipv4",
+                                                             "ip",
                                                              addrs[i],
                                                              port);
                if (composite_nomem(state->addrs[i], c)) return;
+
+               state->names[i] = talloc_strdup(state->names, n);
+               if (composite_nomem(state->names[i], c)) return;
        }
        state->addrs[i] = NULL;
+       state->names[i] = NULL;
 
        composite_done(c);
 }
@@ -392,13 +556,12 @@ static void pipe_handler(struct event_context *ev, struct fd_event *fde,
   getaddrinfo() or dns_lookup() name resolution method - async send
  */
 struct composite_context *resolve_name_dns_ex_send(TALLOC_CTX *mem_ctx,
-                                                  struct event_context *event_ctx,
+                                                  struct tevent_context *event_ctx,
                                                   void *privdata,
                                                   uint32_t flags,
+                                                  uint16_t port,
                                                   struct nbt_name *name,
-                                                  bool do_getaddrinfo,
-                                                  bool do_fallback,
-                                                  bool do_srv)
+                                                  bool do_fallback)
 {
        struct composite_context *c;
        struct dns_ex_state *state;
@@ -408,7 +571,10 @@ struct composite_context *resolve_name_dns_ex_send(TALLOC_CTX *mem_ctx,
        c = composite_create(mem_ctx, event_ctx);
        if (c == NULL) return NULL;
 
-       if (composite_nomem(c->event_ctx, c)) return c;
+       if (flags & RESOLVE_NAME_FLAG_FORCE_NBT) {
+               composite_error(c, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+               return c;
+       }
 
        state = talloc_zero(c, struct dns_ex_state);
        if (composite_nomem(state, c)) return c;
@@ -420,40 +586,40 @@ struct composite_context *resolve_name_dns_ex_send(TALLOC_CTX *mem_ctx,
        /* setup a pipe to chat to our child */
        ret = pipe(fd);
        if (ret == -1) {
-               composite_error(c, map_nt_error_from_unix(errno));
+               composite_error(c, map_nt_error_from_unix_common(errno));
                return c;
        }
 
-       state->do_getaddrinfo = do_getaddrinfo;
        state->do_fallback = do_fallback;
-       state->do_srv = do_srv;
        state->flags = flags;
+       state->port = port;
 
        state->child_fd = fd[0];
        state->event_ctx = c->event_ctx;
 
        /* we need to put the child in our event context so
           we know when the dns_lookup() has finished */
-       state->fde = event_add_fd(c->event_ctx, c, state->child_fd, EVENT_FD_READ, 
+       state->fde = tevent_add_fd(c->event_ctx, c, state->child_fd, TEVENT_FD_READ,
                                  pipe_handler, c);
        if (composite_nomem(state->fde, c)) {
                close(fd[0]);
                close(fd[1]);
                return c;
        }
+       tevent_fd_set_auto_close(state->fde);
 
        state->child = fork();
        if (state->child == (pid_t)-1) {
-               composite_error(c, map_nt_error_from_unix(errno));
+               composite_error(c, map_nt_error_from_unix_common(errno));
                return c;
        }
 
        if (state->child == 0) {
                close(fd[0]);
-               if (state->do_getaddrinfo) {
-                       run_child_getaddrinfo(state, fd[1]);
-               } else {
+               if (state->flags & RESOLVE_NAME_FLAG_FORCE_DNS) {
                        run_child_dns_lookup(state, fd[1]);
+               } else {
+                       run_child_getaddrinfo(state, fd[1]);
                }
                _exit(0);
        }
@@ -470,7 +636,8 @@ struct composite_context *resolve_name_dns_ex_send(TALLOC_CTX *mem_ctx,
 */
 NTSTATUS resolve_name_dns_ex_recv(struct composite_context *c, 
                                  TALLOC_CTX *mem_ctx,
-                                 struct socket_address ***addrs)
+                                 struct socket_address ***addrs,
+                                 char ***names)
 {
        NTSTATUS status;
 
@@ -480,6 +647,9 @@ NTSTATUS resolve_name_dns_ex_recv(struct composite_context *c,
                struct dns_ex_state *state = talloc_get_type(c->private_data,
                                             struct dns_ex_state);
                *addrs = talloc_steal(mem_ctx, state->addrs);
+               if (names) {
+                       *names = talloc_steal(mem_ctx, state->names);
+               }
        }
 
        talloc_free(c);