dirmngr: Add option --standard-resolver.
[gnupg.git] / dirmngr / ks-action.c
index 14de4d6..1087bb5 100644 (file)
@@ -1,5 +1,7 @@
 /* ks-action.c - OpenPGP keyserver actions
  * Copyright (C) 2011 Free Software Foundation, Inc.
+ * Copyright (C) 2011, 2014 Werner Koch
+ * Copyright (C) 2015 g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
@@ -14,7 +16,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>
 #include "misc.h"
 #include "ks-engine.h"
 #include "ks-action.h"
+#if USE_LDAP
+# include "ldap-parse-uri.h"
+#endif
 
-
-/* Copy all data from IN to OUT.  */
-static gpg_error_t
-copy_stream (estream_t in, estream_t out)
+/* Called by the engine's help functions to print the actual help.  */
+gpg_error_t
+ks_print_help (ctrl_t ctrl, const char *text)
 {
-  char buffer[512];
-  size_t nread;
-
-  while (!es_read (in, buffer, sizeof buffer, &nread))
-    {
-      if (!nread)
-        return 0; /* EOF */
-      if (es_write (out, buffer, nread, NULL))
-        break;
-
-    }
-  return gpg_error_from_syserror ();
+  return dirmngr_status_help (ctrl, text);
 }
 
 
 /* Called by the engine's help functions to print the actual help.  */
 gpg_error_t
-ks_print_help (ctrl_t ctrl, const char *text)
+ks_printf_help (ctrl_t ctrl, const char *format, ...)
 {
-  return dirmngr_status_help (ctrl, text);
+  va_list arg_ptr;
+  gpg_error_t err;
+  char *buf;
+
+  va_start (arg_ptr, format);
+  buf = es_vbsprintf (format, arg_ptr);
+  err = buf? 0 : gpg_error_from_syserror ();
+  va_end (arg_ptr);
+  if (!err)
+    err = dirmngr_status_help (ctrl, buf);
+  es_free (buf);
+  return err;
 }
 
 
@@ -71,7 +75,15 @@ ks_action_help (ctrl_t ctrl, const char *url)
     }
   else
     {
-      err = http_parse_uri (&parsed_uri, url, 1);
+#if USE_LDAP
+      if (ldap_uri_p (url))
+       err = ldap_parse_uri (&parsed_uri, url);
+      else
+#endif
+       {
+         err = http_parse_uri (&parsed_uri, url, 1);
+       }
+
       if (err)
         return err;
     }
@@ -84,23 +96,55 @@ ks_action_help (ctrl_t ctrl, const char *url)
     err = ks_finger_help (ctrl, parsed_uri);
   if (!err)
     err = ks_kdns_help (ctrl, parsed_uri);
+#if USE_LDAP
+  if (!err)
+    err = ks_ldap_help (ctrl, parsed_uri);
+#endif
 
   if (!parsed_uri)
     ks_print_help (ctrl,
-                   "(Use the schema followed by a colon for specific help.)");
+                   "(Use an URL for engine specific help.)");
   else
     http_release_parsed_uri (parsed_uri);
   return err;
 }
 
 
+/* Resolve all host names.  This is useful for looking at the status
+   of configured keyservers.  */
+gpg_error_t
+ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers)
+{
+  gpg_error_t err = 0;
+  int any_server = 0;
+  uri_item_t uri;
+
+  for (uri = keyservers; !err && uri; uri = uri->next)
+    {
+      if (uri->parsed_uri->is_http)
+        {
+          any_server = 1;
+          err = ks_hkp_resolve (ctrl, uri->parsed_uri);
+          if (err)
+            break;
+        }
+    }
+
+  if (!any_server)
+    err = gpg_error (GPG_ERR_NO_KEYSERVER);
+  return err;
+}
+
+
 /* Search all configured keyservers for keys matching PATTERNS and
    write the result to the provided output stream.  */
 gpg_error_t
-ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
+ks_action_search (ctrl_t ctrl, uri_item_t keyservers,
+                 strlist_t patterns, estream_t outfp)
 {
   gpg_error_t err = 0;
-  int any = 0;
+  int any_server = 0;
+  int any_results = 0;
   uri_item_t uri;
   estream_t infp;
 
@@ -112,24 +156,53 @@ ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
      parallel and merge them.  We also need to decide what to do with
      errors - it might not be the best idea to ignore an error from
      one server and silently continue with another server.  For now we
-     stop at the first error. */
-  for (uri = ctrl->keyservers; !err && uri; uri = uri->next)
+     stop at the first error, unless the server responds with '404 Not
+     Found', in which case we try the next server.  */
+  for (uri = keyservers; !err && uri; uri = uri->next)
     {
-      if (uri->parsed_uri->is_http)
+      int is_http = uri->parsed_uri->is_http;
+      int is_ldap = 0;
+      unsigned int http_status = 0;
+#if USE_LDAP
+      is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0
+                || strcmp (uri->parsed_uri->scheme, "ldaps") == 0
+                || strcmp (uri->parsed_uri->scheme, "ldapi") == 0);
+#endif
+      if (is_http || is_ldap)
         {
-          any = 1;
-          err = ks_hkp_search (ctrl, uri->parsed_uri, patterns->d, &infp);
+          any_server = 1;
+#if USE_LDAP
+         if (is_ldap)
+           err = ks_ldap_search (ctrl, uri->parsed_uri, patterns->d, &infp);
+         else
+#endif
+           {
+             err = ks_hkp_search (ctrl, uri->parsed_uri, patterns->d,
+                                   &infp, &http_status);
+           }
+
+          if (err == gpg_error (GPG_ERR_NO_DATA)
+              && http_status == 404 /* not found */)
+            {
+              /* No record found.  Clear error and try next server.  */
+              err = 0;
+              continue;
+            }
+
           if (!err)
             {
               err = copy_stream (infp, outfp);
               es_fclose (infp);
+              any_results = 1;
               break;
             }
         }
     }
 
-  if (!any)
+  if (!any_server)
     err = gpg_error (GPG_ERR_NO_KEYSERVER);
+  else if (err == 0 && !any_results)
+    err = gpg_error (GPG_ERR_NO_DATA);
   return err;
 }
 
@@ -137,11 +210,13 @@ ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
 /* Get the requested keys (matching PATTERNS) using all configured
    keyservers and write the result to the provided output stream.  */
 gpg_error_t
-ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
+ks_action_get (ctrl_t ctrl, uri_item_t keyservers,
+              strlist_t patterns, estream_t outfp)
 {
   gpg_error_t err = 0;
   gpg_error_t first_err = 0;
-  int any = 0;
+  int any_server = 0;
+  int any_data = 0;
   strlist_t sl;
   uri_item_t uri;
   estream_t infp;
@@ -155,14 +230,31 @@ ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
      keyservers might not all be fully synced thus it is not clear
      whether the first keyserver has the freshest copy of the key.
      Need to think about a better strategy.  */
-  for (uri = ctrl->keyservers; !err && uri; uri = uri->next)
+  for (uri = keyservers; !err && uri; uri = uri->next)
     {
-      if (uri->parsed_uri->is_http)
+      int is_http = uri->parsed_uri->is_http;
+      int is_ldap = 0;
+
+#if USE_LDAP
+      is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0
+                || strcmp (uri->parsed_uri->scheme, "ldaps") == 0
+                || strcmp (uri->parsed_uri->scheme, "ldapi") == 0);
+#endif
+
+      if (is_http || is_ldap)
         {
-          any = 1;
+          any_server = 1;
           for (sl = patterns; !err && sl; sl = sl->next)
             {
-              err = ks_hkp_get (ctrl, uri->parsed_uri, sl->d, &infp);
+#if USE_LDAP
+             if (is_ldap)
+               err = ks_ldap_get (ctrl, uri->parsed_uri, sl->d, &infp);
+             else
+#endif
+               {
+                 err = ks_hkp_get (ctrl, uri->parsed_uri, sl->d, &infp);
+               }
+
               if (err)
                 {
                   /* It is possible that a server does not carry a
@@ -178,22 +270,26 @@ ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
                   err = copy_stream (infp, outfp);
                   /* Reading from the keyserver should never fail, thus
                      return this error.  */
+                  if (!err)
+                    any_data = 1;
                   es_fclose (infp);
                   infp = NULL;
                 }
             }
         }
+      if (any_data)
+        break; /* Stop loop after a keyserver returned something.  */
     }
 
-  if (!any)
+  if (!any_server)
     err = gpg_error (GPG_ERR_NO_KEYSERVER);
-  else if (!err && first_err)
-    err = first_err; /* fixme: Do we really want to do that?  */
+  else if (!err && first_err && !any_data)
+    err = first_err;
   return err;
 }
 
 
-/* Retrive keys from URL and write the result to the provided output
+/* Retrieve keys from URL and write the result to the provided output
    stream OUTFP.  */
 gpg_error_t
 ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp)
@@ -250,21 +346,47 @@ ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp)
 
 
 /* Send an OpenPGP key to all keyservers.  The key in {DATA,DATALEN}
-   is expected in OpenPGP binary transport format.  */
+   is expected to be in OpenPGP binary transport format.  The metadata
+   in {INFO,INFOLEN} is in colon-separated format (concretely, it is
+   the output of 'for x in keys sigs; do gpg --list-$x --with-colons
+   KEYID; done'.  This function may modify DATA and INFO.  If this is
+   a problem, then the caller should create a copy.  */
 gpg_error_t
-ks_action_put (ctrl_t ctrl, const void *data, size_t datalen)
+ks_action_put (ctrl_t ctrl, uri_item_t keyservers,
+              void *data, size_t datalen,
+              void *info, size_t infolen)
 {
   gpg_error_t err = 0;
   gpg_error_t first_err = 0;
-  int any = 0;
+  int any_server = 0;
   uri_item_t uri;
 
-  for (uri = ctrl->keyservers; !err && uri; uri = uri->next)
+  (void) info;
+  (void) infolen;
+
+  for (uri = keyservers; !err && uri; uri = uri->next)
     {
-      if (uri->parsed_uri->is_http)
+      int is_http = uri->parsed_uri->is_http;
+      int is_ldap = 0;
+
+#if USE_LDAP
+      is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0
+               || strcmp (uri->parsed_uri->scheme, "ldaps") == 0
+               || strcmp (uri->parsed_uri->scheme, "ldapi") == 0);
+#endif
+
+      if (is_http || is_ldap)
         {
-          any = 1;
-          err = ks_hkp_put (ctrl, uri->parsed_uri, data, datalen);
+          any_server = 1;
+#if USE_LDAP
+         if (is_ldap)
+           err = ks_ldap_put (ctrl, uri->parsed_uri, data, datalen,
+                              info, infolen);
+         else
+#endif
+           {
+             err = ks_hkp_put (ctrl, uri->parsed_uri, data, datalen);
+           }
           if (err)
             {
               first_err = err;
@@ -273,9 +395,9 @@ ks_action_put (ctrl_t ctrl, const void *data, size_t datalen)
         }
     }
 
-  if (!any)
+  if (!any_server)
     err = gpg_error (GPG_ERR_NO_KEYSERVER);
   else if (!err && first_err)
-    err = first_err; /* fixme: Do we really want to do that?  */
+    err = first_err;
   return err;
 }