dirmngr: Add basic libdns support
[gnupg.git] / dirmngr / ks-engine-hkp.c
index 8ab0d55..8f53432 100644 (file)
@@ -15,7 +15,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
@@ -92,6 +92,7 @@ struct hostinfo_s
   char *v6addr;      /* A string with the v6 IP address of the host.
                         NULL if NAME has a numeric IP address or no v6
                         address is available.  */
+  unsigned short port; /* The port used by the host, 0 if unknown.  */
   char name[1];      /* The hostname.  */
 };
 
@@ -131,6 +132,7 @@ create_new_hostinfo (const char *name)
   hi->cname = NULL;
   hi->v4addr = NULL;
   hi->v6addr = NULL;
+  hi->port = 0;
 
   /* Add it to the hosttable. */
   for (idx=0; idx < hosttable_size; idx++)
@@ -236,19 +238,157 @@ select_random_host (int *table)
 }
 
 
+/* Figure out if a set of DNS records looks like a pool.  */
+static int
+arecords_is_pool (dns_addrinfo_t aibuf)
+{
+  dns_addrinfo_t ai;
+  int n_v6, n_v4;
+
+  n_v6 = n_v4 = 0;
+  for (ai = aibuf; ai; ai = ai->next)
+    {
+      if (ai->family == AF_INET6)
+        n_v6++;
+      else if (ai->family == AF_INET)
+        n_v4++;
+    }
+
+  return n_v6 > 1 || n_v4 > 1;
+}
+
+
+/* Add the host AI under the NAME into the HOSTTABLE.  If PORT is not
+   zero, it specifies which port to use to talk to the host.  If NAME
+   specifies a pool (as indicated by IS_POOL), update the given
+   reference table accordingly.  */
+static void
+add_host (const char *name, int is_pool,
+          const dns_addrinfo_t ai, unsigned short port,
+          int *reftbl, size_t reftblsize, int *refidx)
+{
+  gpg_error_t tmperr;
+  char *tmphost;
+  int idx, tmpidx;
+  int is_numeric = 0;
+  int i;
+
+  idx = find_hostinfo (name);
+
+  if (!is_pool && !is_ip_address (name))
+    {
+      /* This is a hostname but not a pool.  Use the name
+         as given without going through resolve_dns_addr.  */
+      tmphost = xtrystrdup (name);
+      if (!tmphost)
+        tmperr = gpg_error_from_syserror ();
+      else
+        tmperr = 0;
+    }
+  else
+    {
+      tmperr = resolve_dns_addr (ai->addr, ai->addrlen,
+                                 DNS_WITHBRACKET, &tmphost);
+      if (tmphost && is_ip_address (tmphost))
+        is_numeric = 1;
+    }
+
+  if (tmperr)
+    {
+      log_info ("resolve_dns_addr failed while checking '%s': %s\n",
+                name, gpg_strerror (tmperr));
+    }
+  else if ((*refidx) + 1 >= reftblsize)
+    {
+      log_error ("resolve_dns_addr for '%s': '%s'"
+                 " [index table full - ignored]\n", name, tmphost);
+    }
+  else
+    {
+      if (!is_pool && is_ip_address (name))
+        /* Update the original entry.  */
+        tmpidx = idx;
+      else
+        tmpidx = find_hostinfo (tmphost);
+      log_info ("resolve_dns_addr for '%s': '%s'%s\n",
+                name, tmphost,
+                tmpidx == -1? "" : " [already known]");
+
+      if (tmpidx == -1) /* Create a new entry.  */
+        tmpidx = create_new_hostinfo (tmphost);
+
+      if (tmpidx == -1)
+        {
+          log_error ("map_host for '%s' problem: %s - '%s'"
+                     " [ignored]\n",
+                     name, strerror (errno), tmphost);
+        }
+      else  /* Set or update the entry. */
+        {
+          char *ipaddr = NULL;
+
+          if (port)
+            hosttable[tmpidx]->port = port;
+
+          if (!is_numeric)
+            {
+              xfree (tmphost);
+              tmperr = resolve_dns_addr (ai->addr, ai->addrlen,
+                                         (DNS_NUMERICHOST
+                                          | DNS_WITHBRACKET),
+                                         &tmphost);
+              if (tmperr)
+                log_info ("resolve_dns_addr failed: %s\n",
+                          gpg_strerror (tmperr));
+              else
+                {
+                  ipaddr = tmphost;
+                  tmphost = NULL;
+                }
+            }
+
+          if (ai->family == AF_INET6)
+            {
+              hosttable[tmpidx]->v6 = 1;
+              xfree (hosttable[tmpidx]->v6addr);
+              hosttable[tmpidx]->v6addr = ipaddr;
+            }
+          else if (ai->family == AF_INET)
+            {
+              hosttable[tmpidx]->v4 = 1;
+              xfree (hosttable[tmpidx]->v4addr);
+              hosttable[tmpidx]->v4addr = ipaddr;
+            }
+          else
+            BUG ();
+
+          for (i=0; i < *refidx; i++)
+            if (reftbl[i] == tmpidx)
+              break;
+          if (!(i < *refidx) && tmpidx != idx)
+            reftbl[(*refidx)++] = tmpidx;
+        }
+    }
+  xfree (tmphost);
+}
+
+
 /* Map the host name NAME to the actual to be used host name.  This
    allows us to manage round robin DNS names.  We use our own strategy
    to choose one of the hosts.  For example we skip those hosts which
    failed for some time and we stick to one host for a time
    independent of DNS retry times.  If FORCE_RESELECT is true a new
    host is always selected.  The selected host is stored as a malloced
-   string at R_HOST; on error NULL is stored.  If R_HTTPFLAGS is not
+   string at R_HOST; on error NULL is stored.  If we know the port
+   used by the selected host, a string representation is written to
+   R_PORTSTR, otherwise it is left untouched.  If R_HTTPFLAGS is not
    NULL it will receive flags which are to be passed to http_open.  If
    R_POOLNAME is not NULL a malloced name of the pool is stored or
    NULL if it is not a pool. */
 static gpg_error_t
 map_host (ctrl_t ctrl, const char *name, int force_reselect,
-          char **r_host, unsigned int *r_httpflags, char **r_poolname)
+          char **r_host, char *r_portstr,
+          unsigned int *r_httpflags, char **r_poolname)
 {
   gpg_error_t err = 0;
   hostinfo_t hi;
@@ -286,6 +426,9 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
       int refidx;
       int is_pool = 0;
       char *cname;
+      char *srvrecord;
+      struct srventry *srvs;
+      int srvscount;
 
       reftblsize = 100;
       reftbl = xtrymalloc (reftblsize * sizeof *reftbl);
@@ -302,6 +445,47 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
         }
       hi = hosttable[idx];
 
+      if (!is_ip_address (name))
+        {
+          /* Check for SRV records.  */
+          srvrecord = xtryasprintf ("_hkp._tcp.%s", name);
+          if (srvrecord == NULL)
+            {
+              err = gpg_error_from_syserror ();
+              xfree (reftbl);
+              return err;
+            }
+
+          srvscount = getsrv (srvrecord, &srvs);
+          xfree (srvrecord);
+          if (srvscount < 0)
+            {
+              err = gpg_error_from_syserror ();
+              xfree (reftbl);
+              return err;
+            }
+
+          if (srvscount > 0)
+            {
+              int i;
+              is_pool = srvscount > 1;
+
+              for (i = 0; i < srvscount; i++)
+                {
+                  err = resolve_dns_name (srvs[i].target, 0,
+                                          AF_UNSPEC, SOCK_STREAM,
+                                          &ai, &cname);
+                  if (err)
+                    continue;
+                  dirmngr_tick (ctrl);
+                  add_host (name, is_pool, ai, srvs[i].port,
+                            reftbl, reftblsize, &refidx);
+                }
+
+              xfree (srvs);
+            }
+        }
+
       /* Find all A records for this entry and put them into the pool
          list - if any.  */
       err = resolve_dns_name (name, 0, 0, SOCK_STREAM, &aibuf, &cname);
@@ -312,23 +496,13 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
         }
       else
         {
-          int n_v6, n_v4;
-
           /* First figure out whether this is a pool.  For a pool we
              use a different strategy than for a plain server: We use
              the canonical name of the pool as the virtual host along
              with the IP addresses.  If it is not a pool, we use the
              specified name. */
-          n_v6 = n_v4 = 0;
-          for (ai = aibuf; ai; ai = ai->next)
-            {
-              if (ai->family != AF_INET6)
-                n_v6++;
-              else if (ai->family != AF_INET)
-                n_v4++;
-            }
-          if (n_v6 > 1 || n_v4 > 1)
-            is_pool = 1;
+          if (! is_pool)
+            is_pool = arecords_is_pool (aibuf);
           if (is_pool && cname)
             {
               hi->cname = cname;
@@ -337,109 +511,11 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
 
           for (ai = aibuf; ai; ai = ai->next)
             {
-              gpg_error_t tmperr;
-              char *tmphost;
-              int tmpidx;
-              int is_numeric = 0;
-              int i;
-
               if (ai->family != AF_INET && ai->family != AF_INET6)
                 continue;
-
               dirmngr_tick (ctrl);
 
-              if (!is_pool && !is_ip_address (name))
-                {
-                  /* This is a hostname but not a pool.  Use the name
-                     as given without going through resolve_dns_addr.  */
-                  tmphost = xtrystrdup (name);
-                  if (!tmphost)
-                    tmperr = gpg_error_from_syserror ();
-                  else
-                    tmperr = 0;
-                }
-              else
-                {
-                  tmperr = resolve_dns_addr (ai->addr, ai->addrlen,
-                                             DNS_WITHBRACKET, &tmphost);
-                  if (tmphost && is_ip_address (tmphost))
-                    is_numeric = 1;
-                }
-
-              if (tmperr)
-                {
-                  log_info ("resolve_dns_addr failed while checking '%s': %s\n",
-                            name, gpg_strerror (tmperr));
-                }
-              else if (refidx+1 >= reftblsize)
-                {
-                  log_error ("resolve_dns_addr for '%s': '%s'"
-                             " [index table full - ignored]\n", name, tmphost);
-                }
-              else
-                {
-                  if (!is_pool && is_ip_address (name))
-                    /* Update the original entry.  */
-                    tmpidx = idx;
-                  else
-                    tmpidx = find_hostinfo (tmphost);
-                  log_info ("resolve_dns_addr for '%s': '%s'%s\n",
-                            name, tmphost,
-                            tmpidx == -1? "" : " [already known]");
-
-                  if (tmpidx == -1) /* Create a new entry.  */
-                    tmpidx = create_new_hostinfo (tmphost);
-
-                  if (tmpidx == -1)
-                    {
-                      log_error ("map_host for '%s' problem: %s - '%s'"
-                                 " [ignored]\n",
-                                 name, strerror (errno), tmphost);
-                    }
-                  else  /* Set or update the entry. */
-                    {
-                      char *ipaddr = NULL;
-
-                      if (!is_numeric)
-                        {
-                          xfree (tmphost);
-                          tmperr = resolve_dns_addr (ai->addr, ai->addrlen,
-                                                     (DNS_NUMERICHOST
-                                                      | DNS_WITHBRACKET),
-                                                     &tmphost);
-                          if (tmperr)
-                            log_info ("resolve_dns_addr failed: %s\n",
-                                      gpg_strerror (tmperr));
-                          else
-                            {
-                              ipaddr = tmphost;
-                              tmphost = NULL;
-                            }
-                        }
-
-                      if (ai->family == AF_INET6)
-                        {
-                          hosttable[tmpidx]->v6 = 1;
-                          xfree (hosttable[tmpidx]->v6addr);
-                          hosttable[tmpidx]->v6addr = ipaddr;
-                        }
-                      else if (ai->family == AF_INET)
-                        {
-                          hosttable[tmpidx]->v4 = 1;
-                          xfree (hosttable[tmpidx]->v4addr);
-                          hosttable[tmpidx]->v4addr = ipaddr;
-                        }
-                      else
-                        BUG ();
-
-                      for (i=0; i < refidx; i++)
-                        if (reftbl[i] == tmpidx)
-                          break;
-                      if (!(i < refidx) && tmpidx != idx)
-                        reftbl[refidx++] = tmpidx;
-                    }
-                }
-              xfree (tmphost);
+              add_host (name, is_pool, ai, 0, reftbl, reftblsize, &refidx);
             }
         }
       reftbl[refidx] = -1;
@@ -468,9 +544,9 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
   if (hi->pool)
     {
       /* Deal with the pool name before selecting a host. */
-      if (r_poolname && hi->cname)
+      if (r_poolname)
         {
-          *r_poolname = xtrystrdup (hi->cname);
+          *r_poolname = xtrystrdup (hi->cname? hi->cname : hi->name);
           if (!*r_poolname)
             return gpg_error_from_syserror ();
         }
@@ -544,6 +620,9 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
         }
       return err;
     }
+  if (hi->port)
+    snprintf (r_portstr, 6 /* five digits and the sentinel */,
+              "%hu", hi->port);
   return 0;
 }
 
@@ -795,16 +874,24 @@ make_host_part (ctrl_t ctrl,
 
   *r_hostport = NULL;
 
+  portstr[0] = 0;
+  err = map_host (ctrl, host, force_reselect,
+                  &hostname, portstr, r_httpflags, r_poolname);
+  if (err)
+    return err;
+
   /* Map scheme and port.  */
   if (!strcmp (scheme, "hkps") || !strcmp (scheme,"https"))
     {
       scheme = "https";
-      strcpy (portstr, "443");
+      if (! *portstr)
+        strcpy (portstr, "443");
     }
   else /* HKP or HTTP.  */
     {
       scheme = "http";
-      strcpy (portstr, "11371");
+      if (! *portstr)
+        strcpy (portstr, "11371");
     }
   if (port)
     snprintf (portstr, sizeof portstr, "%hu", port);
@@ -813,11 +900,6 @@ make_host_part (ctrl_t ctrl,
       /*fixme_do_srv_lookup ()*/
     }
 
-  err = map_host (ctrl, host, force_reselect,
-                  &hostname, r_httpflags, r_poolname);
-  if (err)
-    return err;
-
   *r_hostport = strconcat (scheme, "://", hostname, ":", portstr, NULL);
   xfree (hostname);
   if (!*r_hostport)
@@ -891,12 +973,13 @@ ks_hkp_housekeeping (time_t curtime)
    R_FP.  HOSTPORTSTR is only used for diagnostics.  If HTTPHOST is
    not NULL it will be used as HTTP "Host" header.  If POST_CB is not
    NULL a post request is used and that callback is called to allow
-   writing the post data.  */
+   writing the post data.  If R_HTTP_STATUS is not NULL, the http
+   status code will be stored there.  */
 static gpg_error_t
 send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
               const char *httphost, unsigned int httpflags,
               gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value,
-              estream_t *r_fp)
+              estream_t *r_fp, unsigned int *r_http_status)
 {
   gpg_error_t err;
   http_session_t session = NULL;
@@ -907,7 +990,7 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
 
   *r_fp = NULL;
 
-  err = http_session_new (&session, NULL);
+  err = http_session_new (&session, NULL, httphost, HTTP_FLAG_TRUST_DEF);
   if (err)
     goto leave;
   http_session_set_log_cb (session, cert_log_cb);
@@ -967,6 +1050,9 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
       httpflags |= HTTP_FLAG_FORCE_TLS;
     }
 
+  if (r_http_status)
+    *r_http_status = http_get_status_code (http);
+
   switch (http_get_status_code (http))
     {
     case 200:
@@ -1000,6 +1086,10 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
       }
       goto leave;
 
+    case 501:
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      goto leave;
+
     default:
       log_error (_("error accessing '%s': http status %u\n"),
                  request, http_get_status_code (http));
@@ -1071,10 +1161,12 @@ handle_send_request_error (gpg_error_t err, const char *request,
 
 \f
 /* Search the keyserver identified by URI for keys matching PATTERN.
-   On success R_FP has an open stream to read the data.  */
+   On success R_FP has an open stream to read the data.  If
+   R_HTTP_STATUS is not NULL, the http status code will be stored
+   there.  */
 gpg_error_t
 ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
-               estream_t *r_fp)
+               estream_t *r_fp, unsigned int *r_http_status)
 {
   gpg_error_t err;
   KEYDB_SEARCH_DESC desc;
@@ -1114,12 +1206,16 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
       pattern = fprbuf;
       break;
     case KEYDB_SEARCH_MODE_FPR16:
-      bin2hex (desc.u.fpr, 16, fprbuf);
+      fprbuf[0] = '0';
+      fprbuf[1] = 'x';
+      bin2hex (desc.u.fpr, 16, fprbuf+2);
       pattern = fprbuf;
       break;
     case KEYDB_SEARCH_MODE_FPR20:
     case KEYDB_SEARCH_MODE_FPR:
-      bin2hex (desc.u.fpr, 20, fprbuf);
+      fprbuf[0] = '0';
+      fprbuf[1] = 'x';
+      bin2hex (desc.u.fpr, 20, fprbuf+2);
       pattern = fprbuf;
       break;
     default:
@@ -1161,7 +1257,7 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
 
   /* Send the request.  */
   err = send_request (ctrl, request, hostport, httphost, httpflags,
-                      NULL, NULL, &fp);
+                      NULL, NULL, &fp, r_http_status);
   if (handle_send_request_error (err, request, &tries))
     {
       reselect = 1;
@@ -1294,7 +1390,7 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
 
   /* Send the request.  */
   err = send_request (ctrl, request, hostport, httphost, httpflags,
-                      NULL, NULL, &fp);
+                      NULL, NULL, &fp, NULL);
   if (handle_send_request_error (err, request, &tries))
     {
       reselect = 1;
@@ -1402,7 +1498,7 @@ ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen)
 
   /* Send the request.  */
   err = send_request (ctrl, request, hostport, httphost, 0,
-                      put_post_cb, &parm, &fp);
+                      put_post_cb, &parm, &fp, NULL);
   if (handle_send_request_error (err, request, &tries))
     {
       reselect = 1;