Hi all.

Here is a patch that adds a new dns resolver, async-unbound, to libcurl. This 
is a first step towards the goal of full DANE support in curl.

This patch is still a bit crude and not ready for merge:

- It enables unbound by default, instead of using a configure option.
  I'm not fluent in configure so didn't feel comfortable trying to add it.

- It only verifies DNSSEC and does nothing with the TLSA record.

- It probably doesn't always do the right thing with the result of the DNSSEC
  verification.

Still, I've been sitting on this patch for way too long and figured I'd better 
submit it as-is rather than procrastinate any further. I do intend to continute 
working on it, but my track record hasn't been impressive over the last year. 
If anyone feels impatient and wants to pick up the ball and run with it, feel 
free. Just let me know so we avoid duplication of work.

-- 
Björn
>From 64d537d62ed42491b54c5c93a4e3bece87131825 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Stenberg?= <bj...@haxx.se>
Date: Wed, 26 Aug 2015 23:11:32 +0200
Subject: [PATCH] Add first support for the 'unbound' DNSSEC validating
 resolver.

This patch adds the beginnings of support for a new resolver:
 unbound (https://www.unbound.net)
---
 acinclude.m4        |  11 ++
 configure.ac        |   1 +
 include/curl/curl.h |   3 +
 lib/Makefile.inc    |   1 +
 lib/asyn-unbound.c  | 411 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/curl_setup.h    |   6 +
 lib/url.c           |   7 +
 lib/urldata.h       |   2 +
 8 files changed, 442 insertions(+)
 create mode 100644 lib/asyn-unbound.c

diff --git a/acinclude.m4 b/acinclude.m4
index 782f32d..d049620 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -3114,3 +3114,14 @@ use vars qw(
 1;
 _EOF
 ])
+
+
+dnl CURL_CHECK_LIBS_UNBOUND
+dnl -------------------------------------------------
+dnl Check for libraries needed for unbound support,
+dnl and prepended to LIBS any needed libraries.
+
+AC_DEFUN([CURL_CHECK_LIBS_UNBOUND], [
+  #
+  AC_CHECK_LIB(unbound, ub_ctx_delete)
+  ])
diff --git a/configure.ac b/configure.ac
index d269210..5b7d5f5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -685,6 +685,7 @@ AC_HELP_STRING([--enable-libgcc],[use libgcc when linking]),
 )
 
 CURL_CHECK_LIB_XNET
+CURL_CHECK_LIBS_UNBOUND
 
 dnl gethostbyname without lib or in the nsl lib?
 AC_CHECK_FUNC(gethostbyname,
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 11d90f0..0d7e744 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -1648,6 +1648,9 @@ typedef enum {
   /* Set the protocol used when curl is given a URL without a protocol */
   CINIT(DEFAULT_PROTOCOL, OBJECTPOINT, 238),
 
+  /* Specify a file containing DNSSEC trust anchor */
+  CINIT(DNSSEC_TA_FILE, OBJECTPOINT, 232),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index d444a6b..4a2ba6a 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -42,6 +42,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c   \
   pingpong.c rtsp.c curl_threads.c warnless.c hmac.c curl_rtmp.c        \
   openldap.c curl_gethostname.c gopher.c idn_win32.c                    \
   http_negotiate_sspi.c http_proxy.c non-ascii.c asyn-ares.c            \
+  asyn-unbound.c \
   asyn-thread.c curl_gssapi.c curl_ntlm.c curl_ntlm_wb.c                \
   curl_ntlm_core.c curl_ntlm_msgs.c curl_sasl.c curl_multibyte.c        \
   hostcheck.c conncache.c pipeline.c dotdot.c x509asn1.c                \
diff --git a/lib/asyn-unbound.c b/lib/asyn-unbound.c
new file mode 100644
index 0000000..12796db
--- /dev/null
+++ b/lib/asyn-unbound.c
@@ -0,0 +1,411 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2015, Daniel Stenberg, <dan...@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#undef USE_ARES
+#define USE_UNBOUND 1
+#include "curl_setup.h"
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef __VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#ifdef HAVE_PROCESS_H
+#include <process.h>
+#endif
+
+#include <unbound.h>
+
+/* #ifdef CURLRES_ARES */
+
+#include "urldata.h"
+#include "sendf.h"
+#include "hostip.h"
+#include "hash.h"
+#include "share.h"
+#include "strerror.h"
+#include "url.h"
+#include "multiif.h"
+#include "inet_pton.h"
+#include "connect.h"
+#include "select.h"
+#include "progress.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+/* The last #include file should be: */
+#include "memdebug.h"
+
+struct ResolverResults {
+  int async_id;
+};
+
+static struct ub_ctx* unbound_ctx;
+
+/* This is called by ub_process() when resolution is completed */
+static void ubcallback(void* mydata, int err, struct ub_result* result)
+{
+  struct connectdata *conn = mydata;
+
+  conn->async.done = TRUE;
+
+  if(err != 0) {
+    failf(conn->data, "Unbound resolve error: %s\n", ub_strerror(err));
+    return;
+  }
+
+  /* show security status */
+  if(result->secure)
+    infof(conn->data, "DNS result is secure\n");
+  else {
+    if(result->bogus) {
+      infof(conn->data, "DNS %s\n", result->why_bogus);
+      failf(conn->data, "DNS validation failure");
+      conn->async.status = CURLE_COULDNT_RESOLVE_HOST;
+      goto free;
+    }
+    else
+      infof(conn->data, "DNS result is insecure\n");
+  }
+
+  /* show first result */
+  if(result->havedata) {
+    struct Curl_dns_entry *dns;
+
+    switch(result->qtype) {
+    case 1: /* A */
+    case 28: /* AAAA */
+      dns = calloc(sizeof(struct Curl_dns_entry), 1);
+      if(!dns) {
+        failf(conn->data, "Failed allocating dns entry!");
+        return;
+      }
+      dns->addr = Curl_ip2addr(result->qtype == 28 ? AF_INET6 : AF_INET,
+                               result->data[0],
+                               result->qname,
+                               conn->async.port);
+      conn->async.dns = dns;
+      conn->async.done = TRUE;
+      break;
+
+    case 52: /* TLSA */
+      infof(conn->data, "The TLSA of %s is %d %d + %d bytes\n",
+            result->qname,
+            result->data[0][0], result->data[0][1],
+            result->len[0] - 2);
+      break;
+    }
+  }
+  else {
+    if(result->nxdomain)
+      failf(conn->data, "No such domain: %s", result->qname);
+    else
+      failf(conn->data, "No %d data for %s", result->qtype, result->qname);
+    conn->async.done = TRUE;
+    conn->async.status = CURLE_COULDNT_RESOLVE_HOST;
+  }
+
+ free:
+  ub_resolve_free(result);
+}
+
+
+/*
+ * Curl_resolver_global_init() - the generic low-level asynchronous name
+ * resolve API.  Called from curl_global_init() to initialize global resolver
+ * environment.  Initializes ares library.
+ */
+int Curl_resolver_global_init(void)
+{
+  int err;
+
+  /* create context */
+  unbound_ctx = ub_ctx_create();
+  if(!unbound_ctx) {
+    return CURLE_FAILED_INIT;
+  }
+
+  /* read public keys for DNSSEC verification */
+  err=ub_ctx_add_ta_file(unbound_ctx, "unbound-keys");
+  if(err) {
+    return CURLE_FAILED_INIT;
+  }
+
+  return CURLE_OK;
+}
+
+/*
+ * Curl_resolver_global_cleanup()
+ *
+ * Called from curl_global_cleanup() to destroy global resolver environment.
+ * Deinitializes ares library.
+ */
+void Curl_resolver_global_cleanup(void)
+{
+  ub_ctx_delete(unbound_ctx);
+}
+
+/*
+ * Curl_resolver_init()
+ *
+ * Called from curl_easy_init() -> Curl_open() to initialize resolver
+ * URL-state specific environment ('resolver' member of the UrlState
+ * structure).  Fills the passed pointer by the initialized ares_channel.
+ */
+CURLcode Curl_resolver_init(void **resolver)
+{
+  (void)resolver;
+  return CURLE_OK;
+}
+
+/*
+ * Curl_resolver_cleanup()
+ *
+ * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver
+ * URL-state specific environment ('resolver' member of the UrlState
+ * structure).  Destroys the ares channel.
+ */
+void Curl_resolver_cleanup(void *resolver)
+{
+  (void)resolver;
+}
+
+/*
+ * Curl_resolver_duphandle()
+ *
+ * Called from curl_easy_duphandle() to duplicate resolver URL-state specific
+ * environment ('resolver' member of the UrlState structure).  Duplicates the
+ * 'from' ares channel and passes the resulting channel to the 'to' pointer.
+ */
+int Curl_resolver_duphandle(void **to, void *from)
+{
+  (void)to;
+  (void)from;
+  return CURLE_OK;
+}
+
+/*
+ * Cancel all possibly still on-going resolves for this connection.
+ */
+void Curl_resolver_cancel(struct connectdata *conn)
+{
+  if(conn && conn->data && conn->async.os_specific) {
+    struct ResolverResults *res = conn->async.os_specific;
+    ub_cancel(unbound_ctx, res->async_id);
+  }
+}
+
+/*
+ * Curl_resolver_getsock() is called when someone from the outside world
+ * (using curl_multi_fdset()) wants to get our fd_set setup and we're talking
+ * with ares. The caller must make sure that this function is only called when
+ * we have a working ares channel.
+ *
+ * Returns: sockets-in-use-bitmap
+ */
+
+int Curl_resolver_getsock(struct connectdata *conn,
+                          curl_socket_t *socks,
+                          int numsocks)
+
+{
+  (void)conn;
+  (void)socks;
+  (void)numsocks;
+  return ub_fd(unbound_ctx);
+}
+
+/*
+ * Curl_resolver_is_resolved() is called repeatedly to check if a previous
+ * name resolve request has completed. It should also make sure to time-out if
+ * the operation seems to take too long.
+ *
+ * Returns normal CURLcode errors.
+ */
+CURLcode Curl_resolver_is_resolved(struct connectdata *conn,
+                                   struct Curl_dns_entry **dns)
+{
+  int rc = ub_process(unbound_ctx);
+  if(rc) {
+    failf(conn->data, "unbound error %d", rc);
+    return CURLE_FAILED_INIT;
+  }
+
+  if(conn->async.done) {
+    *dns = conn->async.dns;
+    return conn->async.status;
+  }
+  else {
+    *dns = NULL; /* no results yet */
+  }
+  return CURLE_OK;
+}
+
+/*
+ * Curl_resolver_wait_resolv()
+ *
+ * waits for a resolve to finish. This function should be avoided since using
+ * this risk getting the multi interface to "hang".
+ *
+ * If 'entry' is non-NULL, make it point to the resolved dns entry
+ *
+ * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, and
+ * CURLE_OPERATION_TIMEDOUT if a time-out occurred.
+ */
+CURLcode Curl_resolver_wait_resolv(struct connectdata *conn,
+                                   struct Curl_dns_entry **dns)
+{
+  while(!conn->async.done) {
+    struct timeval now = Curl_tvnow();
+    long to = Curl_timeleft(conn->data, &now, TRUE);
+
+    int fd = ub_fd(unbound_ctx);
+
+    struct timeval timeout;
+    fd_set fdset;
+    int rc;
+    timeout.tv_sec  = 0;
+    timeout.tv_usec = to * 1000;
+    FD_ZERO(&fdset);
+    FD_SET(fd, &fdset);
+    rc = select(fd+1, &fdset, NULL, NULL, &timeout);
+    if(rc < 0) {
+      failf(conn->data, "select failed");
+      return CURLE_FAILED_INIT;
+    }
+    else {
+      if(!rc) {
+        /* timeout */
+        return CURLE_OPERATION_TIMEDOUT;
+      }
+      if(rc) {
+        infof(conn->data, "Process1...\n");
+        rc = Curl_resolver_is_resolved(conn, dns);
+        if(dns)
+          break;
+      }
+    }
+  }
+  return CURLE_OK;
+}
+
+Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
+                                         const char *hostname,
+                                         int port,
+                                         int *waitp)
+{
+  char *bufp;
+  int rrtype;
+
+  *waitp = 0; /* default to synchronous response, in case of error */
+
+  switch(conn->ip_version) {
+#ifdef ENABLE_IPV6
+  case CURL_IPRESOLVE_V6:
+    rrtype = 28; /* AAAA */
+    break;
+#endif
+  case CURL_IPRESOLVE_V4:
+  default:
+    rrtype = 1; /* A */
+    break;
+  }
+
+  bufp = strdup(hostname);
+  if(bufp) {
+    int ubrc;
+    struct ResolverResults *res = NULL;
+    Curl_safefree(conn->async.hostname);
+    conn->async.hostname = bufp;
+    conn->async.port = port;
+    conn->async.done = FALSE;   /* not done */
+    conn->async.status = 0;     /* clear */
+    conn->async.dns = NULL;     /* clear */
+    res = calloc(sizeof(struct ResolverResults), 1);
+    if(!res) {
+      Curl_safefree(conn->async.hostname);
+      conn->async.hostname = NULL;
+      return NULL;
+    }
+    conn->async.os_specific = res;
+
+    ubrc = ub_resolve_async(unbound_ctx,
+                            hostname,
+                            rrtype,
+                            1, /* CLASS IN (internet) */
+                            (void*)conn,
+                            ubcallback,
+                            &(res->async_id));
+    if(ubrc != 0) {
+      failf(conn->data, "resolve error: %s", ub_strerror(ubrc));
+      return NULL;
+    }
+    *waitp = 1; /* expect asynchronous response */
+  }
+  return NULL; /* no struct yet */
+}
+
+CURLcode Curl_set_dns_servers(struct SessionHandle *data,
+                              char *servers)
+{
+  (void)data;
+  (void)servers;
+  return CURLE_NOT_BUILT_IN;
+
+}
+
+CURLcode Curl_set_dns_interface(struct SessionHandle *data,
+                                const char *interf)
+{
+  (void)data;
+  (void)interf;
+  return CURLE_NOT_BUILT_IN;
+}
+
+CURLcode Curl_set_dns_local_ip4(struct SessionHandle *data,
+                                const char *local_ip4)
+{
+  (void)data;
+  (void)local_ip4;
+  return CURLE_NOT_BUILT_IN;
+}
+
+CURLcode Curl_set_dns_local_ip6(struct SessionHandle *data,
+                                const char *local_ip6)
+{
+  (void)data;
+  (void)local_ip6;
+  return CURLE_NOT_BUILT_IN;
+}
diff --git a/lib/curl_setup.h b/lib/curl_setup.h
index ab0c139..248ea71 100644
--- a/lib/curl_setup.h
+++ b/lib/curl_setup.h
@@ -531,6 +531,12 @@
 /* now undef the stock libc functions just to avoid them being used */
 #  undef HAVE_GETADDRINFO
 #  undef HAVE_GETHOSTBYNAME
+#elif defined(HAVE_LIBUNBOUND)
+#  define CURLRES_ASYNCH
+#  define CURLRES_UNBOUND
+/* now undef the stock libc functions just to avoid them being used */
+#  undef HAVE_GETADDRINFO
+#  undef HAVE_GETHOSTBYNAME
 #elif defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
 #  define CURLRES_ASYNCH
 #  define CURLRES_THREADED
diff --git a/lib/url.c b/lib/url.c
index d572f01..6998a65 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -2651,6 +2651,13 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
                        va_arg(param, char *));
     break;
 #endif
+  case CURLOPT_DNSSEC_TA_FILE:
+    /*
+     * Option to allow specify a file containing DNSSEC trust anchor
+     */
+    result = setstropt(&data->set.str[STRING_DNSSEC_TA_FILE],
+                       va_arg(param, char *));
+    break;
 
   case CURLOPT_PATH_AS_IS:
     data->set.path_as_is = (0 != va_arg(param, long))?TRUE:FALSE;
diff --git a/lib/urldata.h b/lib/urldata.h
index 3207e61..97ad420 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1417,6 +1417,8 @@ enum dupstring {
 
   STRING_COPYPOSTFIELDS,  /* if POST, set the fields' values here */
 
+  STRING_DNSSEC_TA_FILE,  /* DNSSEC trust anchor file */
+  /* -- end of strings -- */
   STRING_LAST /* not used, just an end-of-list marker */
 };
 
-- 
2.1.4

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html

Reply via email to