dirmngr: Add basic libdns support
[gnupg.git] / dirmngr / ks-engine-hkp.c
index be0280b..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++)
@@ -256,10 +258,13 @@ arecords_is_pool (dns_addrinfo_t aibuf)
 }
 
 
-/* Add the host AI under the NAME into the HOSTTABLE.  Updates the
-   given reference table.  */
+/* 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, const dns_addrinfo_t ai, int is_pool,
+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;
@@ -322,6 +327,9 @@ add_host (const char *name, const dns_addrinfo_t ai, int is_pool,
         {
           char *ipaddr = NULL;
 
+          if (port)
+            hosttable[tmpidx]->port = port;
+
           if (!is_numeric)
             {
               xfree (tmphost);
@@ -371,13 +379,16 @@ add_host (const char *name, const dns_addrinfo_t ai, int is_pool,
    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;
@@ -415,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);
@@ -431,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);
@@ -446,7 +501,8 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
              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. */
-          is_pool = arecords_is_pool (aibuf);
+          if (! is_pool)
+            is_pool = arecords_is_pool (aibuf);
           if (is_pool && cname)
             {
               hi->cname = cname;
@@ -459,7 +515,7 @@ map_host (ctrl_t ctrl, const char *name, int force_reselect,
                 continue;
               dirmngr_tick (ctrl);
 
-              add_host (name, ai, is_pool, reftbl, reftblsize, &refidx);
+              add_host (name, is_pool, ai, 0, reftbl, reftblsize, &refidx);
             }
         }
       reftbl[refidx] = -1;
@@ -488,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 ();
         }
@@ -564,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;
 }
 
@@ -815,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);
@@ -833,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)
@@ -911,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;
@@ -927,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);
@@ -987,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:
@@ -1020,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));
@@ -1091,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;
@@ -1134,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:
@@ -1181,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;
@@ -1314,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;
@@ -1422,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;