Merge branch 'STABLE-BRANCH-2-2' into master
[gnupg.git] / dirmngr / dns-stuff.c
index 63951e5..7324aae 100644 (file)
 #include <config.h>
 #include <sys/types.h>
 #ifdef HAVE_W32_SYSTEM
+# define WIN32_LEAN_AND_MEAN
 # ifdef HAVE_WINSOCK2_H
 #  include <winsock2.h>
 # endif
 # include <windows.h>
+# include <iphlpapi.h>
 #else
 # if HAVE_SYSTEM_RESOLVER
 #  include <netinet/in.h>
 # endif
 # include <netdb.h>
 #endif
+#ifdef HAVE_STAT
+# include <sys/stat.h>
+#endif
 #include <string.h>
 #include <unistd.h>
 
+
 /* William Ahern's DNS library, included as a source copy.  */
 #ifdef USE_LIBDNS
 # include "dns.h"
@@ -65,8 +71,8 @@
 #endif
 
 #include "./dirmngr-err.h"
-#include "util.h"
-#include "host2net.h"
+#include "../common/util.h"
+#include "../common/host2net.h"
 #include "dns-stuff.h"
 
 #ifdef USE_NPTH
@@ -92,9 +98,8 @@
 #ifndef T_SRV
 #define T_SRV 33
 #endif
-#ifndef T_CERT
-# define T_CERT 37
-#endif
+#undef T_CERT
+#define T_CERT 37
 
 /* The standard SOCKS and TOR ports.  */
 #define SOCKS_PORT 1080
 /* The default nameserver used in Tor mode.  */
 #define DEFAULT_NAMESERVER "8.8.8.8"
 
+/* The default timeout in seconds for libdns requests.  */
+#define DEFAULT_TIMEOUT 30
+
+
+#define RESOLV_CONF_NAME "/etc/resolv.conf"
+
+/* Two flags to enable verbose and debug mode.  */
+static int opt_verbose;
+static int opt_debug;
+
+/* The timeout in seconds for libdns requests.  */
+static int opt_timeout;
+
+/* The flag to disable IPv4 access - right now this only skips
+ * returned A records.  */
+static int opt_disable_ipv4;
+
+/* The flag to disable IPv6 access - right now this only skips
+ * returned AAAA records.  */
+static int opt_disable_ipv6;
+
 /* If set force the use of the standard resolver.  */
 static int standard_resolver;
 
@@ -137,6 +163,9 @@ struct libdns_s
 /* If this flag is set, libdns shall be reinited for the next use.  */
 static int libdns_reinit_pending;
 
+/* The Tor port to be used.  */
+static int libdns_tor_port;
+
 #endif /*USE_LIBDNS*/
 
 
@@ -164,7 +193,9 @@ void
 enable_recursive_resolver (int yes)
 {
   recursive_resolver = yes;
+#ifdef USE_LIBDNS
   libdns_reinit_pending = 1;
+#endif
 }
 
 
@@ -180,9 +211,9 @@ recursive_resolver_p (void)
 }
 
 
-/* Sets the module in Tor mode.  Returns 0 is this is possible or an
  error code.  */
-gpg_error_t
+/* Puts this module eternally into Tor mode.  When called agained with
* NEW_CIRCUIT request a new TOR circuit for the next DNS query.  */
+void
 enable_dns_tormode (int new_circuit)
 {
   if (!*tor_socks_user || new_circuit)
@@ -196,7 +227,57 @@ enable_dns_tormode (int new_circuit)
       counter++;
     }
   tor_mode = 1;
-  return 0;
+}
+
+
+/* Disable tor mode.  */
+void
+disable_dns_tormode (void)
+{
+  tor_mode = 0;
+}
+
+
+/* Set verbosity and debug mode for this module. */
+void
+set_dns_verbose (int verbose, int debug)
+{
+  opt_verbose = verbose;
+  opt_debug = debug;
+}
+
+
+/* Set the Disable-IPv4 flag so that the name resolver does not return
+ * A addresses.  */
+void
+set_dns_disable_ipv4 (int yes)
+{
+  opt_disable_ipv4 = !!yes;
+}
+
+
+/* Set the Disable-IPv6 flag so that the name resolver does not return
+ * AAAA addresses.  */
+void
+set_dns_disable_ipv6 (int yes)
+{
+  opt_disable_ipv6 = !!yes;
+}
+
+
+/* Set the timeout for libdns requests to SECONDS.  A value of 0 sets
+ * the default timeout and values are capped at 10 minutes.  */
+void
+set_dns_timeout (int seconds)
+{
+  if (!seconds)
+    seconds = DEFAULT_TIMEOUT;
+  else if (seconds < 1)
+    seconds = 1;
+  else if (seconds > 600)
+    seconds = 600;
+
+  opt_timeout = seconds;
 }
 
 
@@ -209,7 +290,10 @@ set_dns_nameserver (const char *ipaddr)
   strncpy (tor_nameserver, ipaddr? ipaddr : DEFAULT_NAMESERVER,
            sizeof tor_nameserver -1);
   tor_nameserver[sizeof tor_nameserver -1] = 0;
+#ifdef USE_LIBDNS
   libdns_reinit_pending = 1;
+  libdns_tor_port = 0;  /* Start again with the default port.  */
+#endif
 }
 
 
@@ -225,6 +309,8 @@ free_dns_addrinfo (dns_addrinfo_t ai)
     }
 }
 
+
+#ifndef HAVE_W32_SYSTEM
 /* Return H_ERRNO mapped to a gpg-error code.  Will never return 0. */
 static gpg_error_t
 get_h_errno_as_gpg_error (void)
@@ -233,7 +319,7 @@ get_h_errno_as_gpg_error (void)
 
   switch (h_errno)
     {
-    case HOST_NOT_FOUND: ec = GPG_ERR_UNKNOWN_HOST; break;
+    case HOST_NOT_FOUND: ec = GPG_ERR_NO_NAME; break;
     case TRY_AGAIN:      ec = GPG_ERR_TRY_LATER; break;
     case NO_RECOVERY:    ec = GPG_ERR_SERVER_FAILED; break;
     case NO_DATA:        ec = GPG_ERR_NO_DATA; break;
@@ -241,7 +327,7 @@ get_h_errno_as_gpg_error (void)
     }
   return gpg_error (ec);
 }
-
+#endif /*!HAVE_W32_SYSTEM*/
 
 static gpg_error_t
 map_eai_to_gpg_error (int ec)
@@ -310,6 +396,39 @@ libdns_error_to_gpg_error (int serr)
 #endif /*USE_LIBDNS*/
 
 
+/* Return true if resolve.conf changed since it was last loaded.  */
+#ifdef USE_LIBDNS
+static int
+resolv_conf_changed_p (void)
+{
+#if defined(HAVE_W32_SYSTEM) || !defined(HAVE_STAT)
+  return 0;
+#else
+  static time_t last_mtime;
+  const char *fname = RESOLV_CONF_NAME;
+  struct stat statbuf;
+  int changed = 0;
+
+  if (stat (fname, &statbuf))
+    {
+      log_error ("stat'ing '%s' failed: %s\n",
+                 fname, gpg_strerror (gpg_error_from_syserror ()));
+      last_mtime = 1; /* Force a "changed" result the next time stat
+                       * works.  */
+    }
+  else if (!last_mtime)
+    last_mtime = statbuf.st_mtime;
+  else if (last_mtime != statbuf.st_mtime)
+    {
+      changed = 1;
+      last_mtime = statbuf.st_mtime;
+    }
+
+  return changed;
+#endif
+}
+#endif /*USE_LIBDNS*/
+
 #ifdef USE_LIBDNS
 /* Initialize libdns.  Returns 0 on success; prints a diagnostic and
  * returns an error code on failure.  */
@@ -319,7 +438,6 @@ libdns_init (void)
   gpg_error_t err;
   struct libdns_s ld;
   int derr;
-  const char *fname;
   char *cfgstr = NULL;
 
   if (libdns.resolv_conf)
@@ -341,6 +459,9 @@ libdns_init (void)
       if (!*tor_nameserver)
         set_dns_nameserver (NULL);
 
+      if (!libdns_tor_port)
+        libdns_tor_port = TOR_PORT;
+
       cfgstr = xtryasprintf ("[%s]:53", tor_nameserver);
       if (!cfgstr)
         err = gpg_error_from_syserror ();
@@ -356,7 +477,7 @@ libdns_init (void)
       ld.resolv_conf->options.tcp = DNS_RESCONF_TCP_SOCKS;
 
       xfree (cfgstr);
-      cfgstr = xtryasprintf ("[%s]:%d", "127.0.0.1", TOR_PORT);
+      cfgstr = xtryasprintf ("[%s]:%d", "127.0.0.1", libdns_tor_port);
       if (!cfgstr)
         err = gpg_error_from_syserror ();
       else
@@ -371,7 +492,50 @@ libdns_init (void)
     }
   else
     {
-      fname = "/etc/resolv.conf";
+#ifdef HAVE_W32_SYSTEM
+      ULONG ninfo_len;
+      PFIXED_INFO ninfo;
+      PIP_ADDR_STRING pip;
+      int idx;
+
+      ninfo_len = 2048;
+      ninfo = xtrymalloc (ninfo_len);
+      if (!ninfo)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+
+      if (GetNetworkParams (ninfo, &ninfo_len))
+        {
+          log_error ("GetNetworkParms failed: %s\n", w32_strerror (-1));
+          err = gpg_error (GPG_ERR_GENERAL);
+          xfree (ninfo);
+          goto leave;
+        }
+
+      for (idx=0, pip = &(ninfo->DnsServerList);
+           pip && idx < DIM (ld.resolv_conf->nameserver);
+           pip = pip->Next)
+        {
+          if (opt_debug)
+            log_debug ("dns: dnsserver[%d] '%s'\n", idx, pip->IpAddress.String);
+          err = libdns_error_to_gpg_error
+            (dns_resconf_pton (&ld.resolv_conf->nameserver[idx],
+                               pip->IpAddress.String));
+          if (err)
+            log_error ("failed to set nameserver[%d] '%s': %s\n",
+                       idx, pip->IpAddress.String, gpg_strerror (err));
+          else
+            idx++;
+        }
+      xfree (ninfo);
+
+#else /* Unix */
+      const char *fname;
+
+      fname = RESOLV_CONF_NAME;
+      resolv_conf_changed_p (); /* Reset timestamp.  */
       err = libdns_error_to_gpg_error
         (dns_resconf_loadpath (ld.resolv_conf, fname));
       if (err)
@@ -385,33 +549,86 @@ libdns_init (void)
         (dns_nssconf_loadpath (ld.resolv_conf, fname));
       if (err)
         {
-          log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err));
-          goto leave;
+          /* This is not a fatal error: nsswitch.conf is not used on
+           * all systems; assume classic behavior instead.  */
+          if (gpg_err_code (err) != GPG_ERR_ENOENT)
+            log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err));
+          if (opt_debug)
+            log_debug ("dns: fallback resolution order, files then DNS\n");
+          ld.resolv_conf->lookup[0] = 'f';
+          ld.resolv_conf->lookup[1] = 'b';
+          ld.resolv_conf->lookup[2] = '\0';
+          err = GPG_ERR_NO_ERROR;
+        }
+      else if (!strchr (ld.resolv_conf->lookup, 'b'))
+        {
+          /* No DNS resolution type found in the list.  This might be
+           * due to systemd based systems which allow for custom
+           * keywords which are not known to us and thus we do not
+           * know whether DNS is wanted or not.  Because DNS is
+           * important for our infrastructure, we forcefully append
+           * DNS to the end of the list.  */
+          if (strlen (ld.resolv_conf->lookup)+2 < sizeof ld.resolv_conf->lookup)
+            {
+              if (opt_debug)
+                log_debug ("dns: appending DNS to resolution order\n");
+              strcat (ld.resolv_conf->lookup, "b");
+            }
+          else
+            log_error ("failed to append DNS to resolution order\n");
         }
+
+#endif /* Unix */
     }
 
   ld.hosts = dns_hosts_open (&derr);
   if (!ld.hosts)
     {
-      log_error ("failed to load hosts file: %s\n", gpg_strerror (err));
       err = libdns_error_to_gpg_error (derr);
+      log_error ("failed to initialize hosts file: %s\n", gpg_strerror (err));
       goto leave;
     }
 
+  {
+#if HAVE_W32_SYSTEM
+    char *hosts_path = xtryasprintf ("%s\\System32\\drivers\\etc\\hosts",
+                                     getenv ("SystemRoot"));
+    if (! hosts_path)
+      {
+        err = gpg_error_from_syserror ();
+        goto leave;
+      }
+
+    derr = dns_hosts_loadpath (ld.hosts, hosts_path);
+    xfree (hosts_path);
+#else
+    derr = dns_hosts_loadpath (ld.hosts, "/etc/hosts");
+#endif
+    if (derr)
+      {
+        err = libdns_error_to_gpg_error (derr);
+        log_error ("failed to load hosts file: %s\n", gpg_strerror (err));
+        err = 0; /* Do not bail out - having no /etc/hosts is legal.  */
+      }
+  }
+
   /* dns_hints_local for stub mode, dns_hints_root for recursive.  */
   ld.hints = (recursive_resolver
               ? dns_hints_root  (ld.resolv_conf, &derr)
               : dns_hints_local (ld.resolv_conf, &derr));
   if (!ld.hints)
     {
-      log_error ("failed to load DNS hints: %s\n", gpg_strerror (err));
       err = libdns_error_to_gpg_error (derr);
+      log_error ("failed to load DNS hints: %s\n", gpg_strerror (err));
       goto leave;
     }
 
   /* All fine.  Make the data global.  */
   libdns = ld;
 
+  if (opt_debug)
+    log_debug ("dns: libdns initialized%s\n", tor_mode?" (tor mode)":"");
+
  leave:
   xfree (cfgstr);
   return err;
@@ -437,20 +654,26 @@ libdns_deinit (void)
 }
 #endif /*USE_LIBDNS*/
 
+
 /* SIGHUP action handler for this module.  With FORCE set objects are
  * all immediately released. */
 void
 reload_dns_stuff (int force)
 {
+#ifdef USE_LIBDNS
   if (force)
     {
-#ifdef USE_LIBDNS
       libdns_deinit ();
-#endif
       libdns_reinit_pending = 0;
     }
   else
-    libdns_reinit_pending = 1;
+    {
+      libdns_reinit_pending = 1;
+      libdns_tor_port = 0;  /* Start again with the default port.  */
+    }
+#else
+  (void)force;
+#endif
 }
 
 
@@ -469,6 +692,14 @@ libdns_res_open (struct dns_resolver **r_res)
 
   *r_res = NULL;
 
+  /* Force a reload if resolv.conf has changed.  */
+  if (resolv_conf_changed_p ())
+    {
+      if (opt_debug)
+        log_debug ("dns: resolv.conf changed - forcing reload\n");
+      libdns_reinit_pending = 1;
+    }
+
   if (libdns_reinit_pending)
     {
       libdns_reinit_pending = 0;
@@ -479,6 +710,9 @@ libdns_res_open (struct dns_resolver **r_res)
   if (err)
     return err;
 
+  if (!opt_timeout)
+    set_dns_timeout (0);
+
   res = dns_res_open (libdns.resolv_conf, libdns.hosts, libdns.hints, NULL,
                       dns_opts (.socks_host     = &libdns.socks_host,
                                 .socks_user     = tor_socks_user,
@@ -494,6 +728,28 @@ libdns_res_open (struct dns_resolver **r_res)
 
 
 #ifdef USE_LIBDNS
+/* Helper to test whether we need to try again after having switched
+ * the Tor port.  */
+static int
+libdns_switch_port_p (gpg_error_t err)
+{
+  if (tor_mode && gpg_err_code (err) == GPG_ERR_ECONNREFUSED
+      && libdns_tor_port == TOR_PORT)
+    {
+      /* Switch port and try again.  */
+      if (opt_debug)
+        log_debug ("dns: switching from SOCKS port %d to %d\n",
+                   TOR_PORT, TOR_PORT2);
+      libdns_tor_port = TOR_PORT2;
+      libdns_reinit_pending = 1;
+      return 1;
+    }
+  return 0;
+}
+#endif /*USE_LIBDNS*/
+
+
+#ifdef USE_LIBDNS
 /* Wrapper around dns_res_submit.  */
 static gpg_error_t
 libdns_res_submit (struct dns_resolver *res, const char *qname,
@@ -514,7 +770,7 @@ libdns_res_wait (struct dns_resolver *res)
   while ((err = libdns_error_to_gpg_error (dns_res_check (res)))
          && gpg_err_code (err) == GPG_ERR_EAGAIN)
     {
-      if (dns_res_elapsed (res) > 30)
+      if (dns_res_elapsed (res) > opt_timeout)
         {
           err = gpg_error (GPG_ERR_DNS_TIMEOUT);
           break;
@@ -545,6 +801,7 @@ resolve_name_libdns (const char *name, unsigned short port,
   struct addrinfo *ent;
   char portstr_[21];
   char *portstr = NULL;
+  char *namebuf = NULL;
   int derr;
 
   *r_dai = NULL;
@@ -568,6 +825,25 @@ resolve_name_libdns (const char *name, unsigned short port,
   if (err)
     goto leave;
 
+
+  if (is_ip_address (name))
+    {
+      hints.ai_flags |= AI_NUMERICHOST;
+      /* libdns does not grok brackets - remove them.  */
+      if (*name == '[' && name[strlen(name)-1] == ']')
+        {
+          namebuf = xtrymalloc (strlen (name));
+          if (!namebuf)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          strcpy (namebuf, name+1);
+          namebuf[strlen (namebuf)-1] = 0;
+          name = namebuf;
+        }
+    }
+
   ai = dns_ai_open (name, portstr, 0, &hints, res, &derr);
   if (!ai)
     {
@@ -587,7 +863,7 @@ resolve_name_libdns (const char *name, unsigned short port,
         }
       if (gpg_err_code (err) == GPG_ERR_EAGAIN)
         {
-          if (dns_ai_elapsed (ai) > 30)
+          if (dns_ai_elapsed (ai) > opt_timeout)
             {
               err = gpg_error (GPG_ERR_DNS_TIMEOUT);
               goto leave;
@@ -609,9 +885,13 @@ resolve_name_libdns (const char *name, unsigned short port,
               err = gpg_error_from_syserror ();
               goto leave;
             }
+          /* Libdns appends the root zone part which is problematic
+           * for most other functions - strip it.  */
+          if (**r_canonname && (*r_canonname)[strlen (*r_canonname)-1] == '.')
+            (*r_canonname)[strlen (*r_canonname)-1] = 0;
         }
 
-      dai = xtrymalloc (sizeof *dai + ent->ai_addrlen -1);
+      dai = xtrymalloc (sizeof *dai);
       if (dai == NULL)
         {
           err = gpg_error_from_syserror ();
@@ -645,6 +925,7 @@ resolve_name_libdns (const char *name, unsigned short port,
   else
     *r_dai = daihead;
 
+  xfree (namebuf);
   return err;
 }
 #endif /*USE_LIBDNS*/
@@ -674,13 +955,15 @@ resolve_name_standard (const char *name, unsigned short port,
   hints.ai_flags = AI_ADDRCONFIG;
   if (r_canonname)
     hints.ai_flags |= AI_CANONNAME;
+  if (is_ip_address (name))
+    hints.ai_flags |= AI_NUMERICHOST;
 
   if (port)
     snprintf (portstr, sizeof portstr, "%hu", port);
   else
     *portstr = 0;
 
-  /* We can't use the the AI_IDN flag because that does the conversion
+  /* We can't use the AI_IDN flag because that does the conversion
      using the current locale.  However, GnuPG always used UTF-8.  To
      support IDN we would need to make use of the libidn API.  */
   ret = getaddrinfo (name, *portstr? portstr : NULL, &hints, &aibuf);
@@ -727,8 +1010,12 @@ resolve_name_standard (const char *name, unsigned short port,
     {
       if (ai->ai_family != AF_INET6 && ai->ai_family != AF_INET)
         continue;
+      if (opt_disable_ipv4 && ai->ai_family == AF_INET)
+        continue;
+      if (opt_disable_ipv6 && ai->ai_family == AF_INET6)
+        continue;
 
-      dai = xtrymalloc (sizeof *dai + ai->ai_addrlen - 1);
+      dai = xtrymalloc (sizeof *dai);
       dai->family = ai->ai_family;
       dai->socktype = ai->ai_socktype;
       dai->protocol = ai->ai_protocol;
@@ -756,9 +1043,181 @@ resolve_name_standard (const char *name, unsigned short port,
 }
 
 
+/* This a wrapper around getaddrinfo with slightly different semantics.
+   NAME is the name to resolve.
+   PORT is the requested port or 0.
+   WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4.
+   WANT_SOCKETTYPE is either SOCK_STREAM or SOCK_DGRAM.
+
+   On success the result is stored in a linked list with the head
+   stored at the address R_AI; the caller must call gpg_addrinfo_free
+   on this.  If R_CANONNAME is not NULL the official name of the host
+   is stored there as a malloced string; if that name is not available
+   NULL is stored.  */
+gpg_error_t
+resolve_dns_name (const char *name, unsigned short port,
+                  int want_family, int want_socktype,
+                  dns_addrinfo_t *r_ai, char **r_canonname)
+{
+  gpg_error_t err;
+
+#ifdef USE_LIBDNS
+  if (!standard_resolver)
+    {
+      err = resolve_name_libdns (name, port, want_family, want_socktype,
+                                  r_ai, r_canonname);
+      if (err && libdns_switch_port_p (err))
+        err = resolve_name_libdns (name, port, want_family, want_socktype,
+                                   r_ai, r_canonname);
+    }
+  else
+#endif /*USE_LIBDNS*/
+    err = resolve_name_standard (name, port, want_family, want_socktype,
+                                 r_ai, r_canonname);
+  if (opt_debug)
+    log_debug ("dns: resolve_dns_name(%s): %s\n", name, gpg_strerror (err));
+  return err;
+}
+
+
+#ifdef USE_LIBDNS
+/* Resolve an address using libdns.  */
+static gpg_error_t
+resolve_addr_libdns (const struct sockaddr_storage *addr, int addrlen,
+                     unsigned int flags, char **r_name)
+{
+  gpg_error_t err;
+  char host[DNS_D_MAXNAME + 1];
+  struct dns_resolver *res = NULL;
+  struct dns_packet *ans = NULL;
+  struct dns_ptr ptr;
+  int derr;
+
+  *r_name = NULL;
+
+  /* First we turn ADDR into a DNS name (with ".arpa" suffix).  */
+  err = 0;
+  if (addr->ss_family == AF_INET6)
+    {
+      const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)addr;
+      if (!dns_aaaa_arpa (host, sizeof host, (void*)&a6->sin6_addr))
+        err = gpg_error (GPG_ERR_INV_OBJ);
+    }
+  else if (addr->ss_family == AF_INET)
+    {
+      const struct sockaddr_in *a4 = (const struct sockaddr_in *)addr;
+      if (!dns_a_arpa (host, sizeof host, (void*)&a4->sin_addr))
+        err = gpg_error (GPG_ERR_INV_OBJ);
+    }
+  else
+    err = gpg_error (GPG_ERR_EAFNOSUPPORT);
+  if (err)
+    goto leave;
+
+
+  err = libdns_res_open (&res);
+  if (err)
+    goto leave;
+
+  err = libdns_res_submit (res, host, DNS_T_PTR, DNS_C_IN);
+  if (err)
+    goto leave;
+
+  err = libdns_res_wait (res);
+  if (err)
+    goto leave;
+
+  ans = dns_res_fetch (res, &derr);
+  if (!ans)
+    {
+      err = libdns_error_to_gpg_error (derr);
+      goto leave;
+    }
+
+  /* Check the rcode.  */
+  switch (dns_p_rcode (ans))
+    {
+    case DNS_RC_NOERROR:
+      break;
+    case DNS_RC_NXDOMAIN:
+      err = gpg_error (GPG_ERR_NO_NAME);
+      break;
+    default:
+      err = GPG_ERR_SERVER_FAILED;
+      goto leave;
+    }
+
+  /* Parse the result.  */
+  if (!err)
+    {
+      struct dns_rr rr;
+      struct dns_rr_i rri;
+
+      memset (&rri, 0, sizeof rri);
+      dns_rr_i_init (&rri, ans);
+      rri.section = DNS_S_ALL & ~DNS_S_QD;
+      rri.name    = host;
+      rri.type    = DNS_T_PTR;
+
+      if (!dns_rr_grep (&rr, 1, &rri, ans, &derr))
+        {
+          err = gpg_error (GPG_ERR_NOT_FOUND);
+          goto leave;
+        }
+
+      err = libdns_error_to_gpg_error (dns_ptr_parse (&ptr, &rr, ans));
+      if (err)
+        goto leave;
+
+      /* Copy result.  */
+      *r_name = xtrystrdup (ptr.host);
+      if (!*r_name)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      /* Libdns appends the root zone part which is problematic
+       * for most other functions - strip it.  */
+      if (**r_name && (*r_name)[strlen (*r_name)-1] == '.')
+        (*r_name)[strlen (*r_name)-1] = 0;
+    }
+  else /* GPG_ERR_NO_NAME */
+    {
+      char *buffer, *p;
+      int buflen;
+      int ec;
+
+      buffer = ptr.host;
+      buflen = sizeof ptr.host;
+
+      p = buffer;
+      if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET))
+        {
+          *p++ = '[';
+          buflen -= 2;
+        }
+      ec = getnameinfo ((const struct sockaddr *)addr,
+                        addrlen, p, buflen, NULL, 0, NI_NUMERICHOST);
+      if (ec)
+        {
+          err = map_eai_to_gpg_error (ec);
+          goto leave;
+        }
+      if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET))
+        strcat (buffer, "]");
+    }
+
+ leave:
+  dns_free (ans);
+  dns_res_close (res);
+  return err;
+}
+#endif /*USE_LIBDNS*/
+
+
 /* Resolve an address using the standard system function.  */
 static gpg_error_t
-resolve_addr_standard (const struct sockaddr *addr, int addrlen,
+resolve_addr_standard (const struct sockaddr_storage *addr, int addrlen,
                        unsigned int flags, char **r_name)
 {
   gpg_error_t err;
@@ -776,20 +1235,22 @@ resolve_addr_standard (const struct sockaddr *addr, int addrlen,
   if ((flags & DNS_NUMERICHOST) || tor_mode)
     ec = EAI_NONAME;
   else
-    ec = getnameinfo (addr, addrlen, buffer, buflen, NULL, 0, NI_NAMEREQD);
+    ec = getnameinfo ((const struct sockaddr *)addr,
+                      addrlen, buffer, buflen, NULL, 0, NI_NAMEREQD);
 
   if (!ec && *buffer == '[')
     ec = EAI_FAIL;  /* A name may never start with a bracket.  */
   else if (ec == EAI_NONAME)
     {
       p = buffer;
-      if (addr->sa_family == AF_INET6 && (flags & DNS_WITHBRACKET))
+      if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET))
         {
           *p++ = '[';
           buflen -= 2;
         }
-      ec = getnameinfo (addr, addrlen, p, buflen, NULL, 0, NI_NUMERICHOST);
-      if (!ec && addr->sa_family == AF_INET6 && (flags & DNS_WITHBRACKET))
+      ec = getnameinfo ((const struct sockaddr *)addr,
+                        addrlen, p, buflen, NULL, 0, NI_NUMERICHOST);
+      if (!ec && addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET))
         strcat (buffer, "]");
     }
 
@@ -816,43 +1277,35 @@ resolve_addr_standard (const struct sockaddr *addr, int addrlen,
 }
 
 
-/* This a wrapper around getaddrinfo with slightly different semantics.
-   NAME is the name to resolve.
-   PORT is the requested port or 0.
-   WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4.
-   WANT_SOCKETTYPE is either SOCK_STREAM or SOCK_DGRAM.
-
-   On success the result is stored in a linked list with the head
-   stored at the address R_AI; the caller must call gpg_addrinfo_free
-   on this.  If R_CANONNAME is not NULL the official name of the host
-   is stored there as a malloced string; if that name is not available
-   NULL is stored.  */
+/* A wrapper around getnameinfo.  */
 gpg_error_t
-resolve_dns_name (const char *name, unsigned short port,
-                  int want_family, int want_socktype,
-                  dns_addrinfo_t *r_ai, char **r_canonname)
+resolve_dns_addr (const struct sockaddr_storage *addr, int addrlen,
+                  unsigned int flags, char **r_name)
 {
+  gpg_error_t err;
+
 #ifdef USE_LIBDNS
-  if (!standard_resolver)
-    return resolve_name_libdns (name, port, want_family, want_socktype,
-                                r_ai, r_canonname);
+  /* Note that we divert to the standard resolver for NUMERICHOST.  */
+  if (!standard_resolver && !(flags & DNS_NUMERICHOST))
+    {
+      err = resolve_addr_libdns (addr, addrlen, flags, r_name);
+      if (err && libdns_switch_port_p (err))
+        err = resolve_addr_libdns (addr, addrlen, flags, r_name);
+    }
+  else
 #endif /*USE_LIBDNS*/
+    err = resolve_addr_standard (addr, addrlen, flags, r_name);
 
-  return resolve_name_standard (name, port, want_family, want_socktype,
-                                r_ai, r_canonname);
-}
-
-
-gpg_error_t
-resolve_dns_addr (const struct sockaddr *addr, int addrlen,
-                  unsigned int flags, char **r_name)
-{
-  return resolve_addr_standard (addr, addrlen, flags, r_name);
+  if (opt_debug)
+    log_debug ("dns: resolve_dns_addr(): %s\n", gpg_strerror (err));
+  return err;
 }
 
 
-/* Check whether NAME is an IP address.  Returns true if it is either
-   an IPv6 or IPv4 numerical address.  */
+/* Check whether NAME is an IP address.  Returns a true if it is
+ * either an IPv6 or a IPv4 numerical address.  The actual return
+ * values can also be used to identify whether it is v4 or v6: The
+ * true value will surprisingly be 4 for IPv4 and 6 for IPv6.  */
 int
 is_ip_address (const char *name)
 {
@@ -860,8 +1313,8 @@ is_ip_address (const char *name)
   int ndots, dblcol, n;
 
   if (*name == '[')
-    return 1; /* yes: A legal DNS name may not contain this character;
-                 this mut be bracketed v6 address.  */
+    return 6; /* yes: A legal DNS name may not contain this character;
+                 this must be bracketed v6 address.  */
   if (*name == '.')
     return 0; /* No.  A leading dot is not a valid IP address.  */
 
@@ -893,7 +1346,7 @@ is_ip_address (const char *name)
   if (ndots > 7)
     return 0; /* No: Too many colons.  */
   else if (ndots > 1)
-    return 1; /* Yes: At least 2 colons indicate an v6 address.  */
+    return 6; /* Yes: At least 2 colons indicate an v6 address.  */
 
  legacy:
   /* Check whether it is legacy IP address.  */
@@ -903,7 +1356,7 @@ is_ip_address (const char *name)
       if (*s == '.')
         {
           if (s[1] == '.')
-            return 0; /* No:  Douple dot. */
+            return 0; /* No:  Double dot. */
           if (atoi (s+1) > 255)
             return 0; /* No:  Ipv4 byte value too large.  */
           ndots++;
@@ -914,7 +1367,7 @@ is_ip_address (const char *name)
       else if (++n > 3)
         return 0; /* No: More than 3 digits.  */
     }
-  return !!(ndots == 3);
+  return (ndots == 3)? 4 : 0;
 }
 
 
@@ -948,7 +1401,7 @@ get_dns_cert_libdns (const char *name, int want_certtype,
   int derr;
   int qtype;
 
-  /* Gte the query type from WANT_CERTTYPE (which in general indicates
+  /* Get the query type from WANT_CERTTYPE (which in general indicates
    * the subtype we want). */
   qtype = (want_certtype < DNS_CERTTYPE_RRBASE
            ? T_CERT
@@ -1314,7 +1767,7 @@ get_dns_cert_standard (const char *name, int want_certtype,
    found, the malloced data is returned at (R_KEY, R_KEYLEN) and
    the other return parameters are set to NULL/0.  If an IPGP CERT
    record was found the fingerprint is stored as an allocated block at
-   R_FPR and its length at R_FPRLEN; an URL is is allocated as a
+   R_FPR and its length at R_FPRLEN; an URL is allocated as a
    string and returned at R_URL.  If WANT_CERTTYPE is 0 this function
    returns the first CERT found with a supported type; it is expected
    that only one CERT record is used.  If WANT_CERTTYPE is one of the
@@ -1325,6 +1778,8 @@ get_dns_cert (const char *name, int want_certtype,
               void **r_key, size_t *r_keylen,
               unsigned char **r_fpr, size_t *r_fprlen, char **r_url)
 {
+  gpg_error_t err;
+
   if (r_key)
     *r_key = NULL;
   if (r_keylen)
@@ -1335,12 +1790,21 @@ get_dns_cert (const char *name, int want_certtype,
 
 #ifdef USE_LIBDNS
   if (!standard_resolver)
-    return get_dns_cert_libdns (name, want_certtype, r_key, r_keylen,
-                                r_fpr, r_fprlen, r_url);
+    {
+      err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen,
+                                 r_fpr, r_fprlen, r_url);
+      if (err && libdns_switch_port_p (err))
+        err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen,
+                                   r_fpr, r_fprlen, r_url);
+    }
+  else
 #endif /*USE_LIBDNS*/
+    err = get_dns_cert_standard (name, want_certtype, r_key, r_keylen,
+                                 r_fpr, r_fprlen, r_url);
 
-  return get_dns_cert_standard (name, want_certtype, r_key, r_keylen,
-                                r_fpr, r_fprlen, r_url);
+  if (opt_debug)
+    log_debug ("dns: get_dns_cert(%s): %s\n", name, gpg_strerror (err));
+  return err;
 }
 
 
@@ -1362,7 +1826,7 @@ priosort(const void *a,const void *b)
  * R_COUNT.  */
 #ifdef USE_LIBDNS
 static gpg_error_t
-getsrv_libdns (const char *name, struct srventry **list, int *r_count)
+getsrv_libdns (const char *name, struct srventry **list, unsigned int *r_count)
 {
   gpg_error_t err;
   struct dns_resolver *res = NULL;
@@ -1371,7 +1835,7 @@ getsrv_libdns (const char *name, struct srventry **list, int *r_count)
   struct dns_rr_i rri;
   char host[DNS_D_MAXNAME + 1];
   int derr;
-  int srvcount=0;
+  unsigned int srvcount = 0;
 
   err = libdns_res_open (&res);
   if (err)
@@ -1438,6 +1902,10 @@ getsrv_libdns (const char *name, struct srventry **list, int *r_count)
       srv->weight   = dsrv.weight;
       srv->port     = dsrv.port;
       mem2str (srv->target, dsrv.target, sizeof srv->target);
+      /* Libdns appends the root zone part which is problematic for
+       * most other functions - strip it.  */
+      if (*srv->target && (srv->target)[strlen (srv->target)-1] == '.')
+        (srv->target)[strlen (srv->target)-1] = 0;
     }
 
   *r_count = srvcount;
@@ -1459,7 +1927,8 @@ getsrv_libdns (const char *name, struct srventry **list, int *r_count)
  * expected that NULL is stored at the address of LIST and 0 is stored
  * at the address of R_COUNT.  */
 static gpg_error_t
-getsrv_standard (const char *name, struct srventry **list, int *r_count)
+getsrv_standard (const char *name,
+                 struct srventry **list, unsigned int *r_count)
 {
 #ifdef HAVE_SYSTEM_RESOLVER
   union {
@@ -1471,7 +1940,7 @@ getsrv_standard (const char *name, struct srventry **list, int *r_count)
   unsigned char *pt, *emsg;
   int r, rc;
   u16 dlen;
-  int srvcount=0;
+  unsigned int srvcount = 0;
   u16 count;
 
   /* Do not allow a query using the standard resolver in Tor mode.  */
@@ -1582,24 +2051,54 @@ getsrv_standard (const char *name, struct srventry **list, int *r_count)
 }
 
 
-int
-getsrv (const char *name, struct srventry **list)
+/* Query a SRV record for SERVICE and PROTO for NAME.  If SERVICE is
+ * NULL, NAME is expected to contain the full query name.  Note that
+ * we do not return NONAME but simply store 0 at R_COUNT.  On error an
+ * error code is returned and 0 stored at R_COUNT.  */
+gpg_error_t
+get_dns_srv (const char *name, const char *service, const char *proto,
+             struct srventry **list, unsigned int *r_count)
 {
   gpg_error_t err;
-  int srvcount;
+  char *namebuffer = NULL;
+  unsigned int srvcount;
   int i;
 
   *list = NULL;
+  *r_count = 0;
   srvcount = 0;
+
+  /* If SERVICE is given construct the query from it and PROTO.  */
+  if (service)
+    {
+      namebuffer = xtryasprintf ("_%s._%s.%s",
+                                 service, proto? proto:"tcp", name);
+      if (!namebuffer)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      name = namebuffer;
+    }
+
+
 #ifdef USE_LIBDNS
   if (!standard_resolver)
-    err = getsrv_libdns (name, list, &srvcount);
+    {
+      err = getsrv_libdns (name, list, &srvcount);
+      if (err && libdns_switch_port_p (err))
+        err = getsrv_libdns (name, list, &srvcount);
+    }
   else
 #endif /*USE_LIBDNS*/
     err = getsrv_standard (name, list, &srvcount);
 
   if (err)
-    return -1;  /* Ugly.  FIXME: Return an error code. */
+    {
+      if (gpg_err_code (err) == GPG_ERR_NO_NAME)
+        err = 0;
+      goto leave;
+    }
 
   /* Now we have an array of all the srv records. */
 
@@ -1674,7 +2173,18 @@ getsrv (const char *name, struct srventry **list)
         }
     }
 
-  return srvcount;
+ leave:
+  if (opt_debug)
+    {
+      if (err)
+        log_debug ("dns: getsrv(%s): %s\n", name, gpg_strerror (err));
+      else
+        log_debug ("dns: getsrv(%s) -> %u records\n", name, srvcount);
+    }
+  if (!err)
+    *r_count = srvcount;
+  xfree (namebuffer);
+  return err;
 }
 
 
@@ -1734,6 +2244,13 @@ get_dns_cname_libdns (const char *name, char **r_cname)
   *r_cname = xtrystrdup (cname.host);
   if (!*r_cname)
     err = gpg_error_from_syserror ();
+  else
+    {
+      /* Libdns appends the root zone part which is problematic
+       * for most other functions - strip it.  */
+      if (**r_cname && (*r_cname)[strlen (*r_cname)-1] == '.')
+        (*r_cname)[strlen (*r_cname)-1] = 0;
+    }
 
  leave:
   dns_free (ans);
@@ -1830,12 +2347,24 @@ get_dns_cname_standard (const char *name, char **r_cname)
 gpg_error_t
 get_dns_cname (const char *name, char **r_cname)
 {
+  gpg_error_t err;
+
   *r_cname = NULL;
 
 #ifdef USE_LIBDNS
   if (!standard_resolver)
-    return get_dns_cname_libdns (name, r_cname);
+    {
+      err = get_dns_cname_libdns (name, r_cname);
+      if (err && libdns_switch_port_p (err))
+        err = get_dns_cname_libdns (name, r_cname);
+      return err;
+    }
 #endif /*USE_LIBDNS*/
 
-  return get_dns_cname_standard (name, r_cname);
+  err = get_dns_cname_standard (name, r_cname);
+  if (opt_debug)
+    log_debug ("get_dns_cname(%s)%s%s\n", name,
+               err ? ": " : " -> ",
+               err ? gpg_strerror (err) : *r_cname);
+  return err;
 }