Imported Upstream version 2.4.3
[platform/upstream/gpg2.git] / dirmngr / ks-engine-ldap.c
index 6d520e9..c2a2105 100644 (file)
@@ -1,7 +1,7 @@
 /* ks-engine-ldap.c - talk to a LDAP keyserver
  * Copyright (C) 2001, 2002, 2004, 2005, 2006
  *               2007  Free Software Foundation, Inc.
- * Copyright (C) 2015  g10 Code GmbH
+ * Copyright (C) 2015, 2020, 2023  g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
-#ifdef HAVE_GETOPT_H
-# include <getopt.h>
-#endif
 #include <stdlib.h>
-#include <errno.h>
-#include <assert.h>
-
-#ifdef _WIN32
-# include <winsock2.h>
-# include <winldap.h>
-#else
-# ifdef NEED_LBER_H
-#  include <lber.h>
+#include <npth.h>
+#ifdef HAVE_W32_SYSTEM
+# ifndef WINVER
+#  define WINVER 0x0500  /* Same as in common/sysutils.c */
 # endif
-/* For OpenLDAP, to enable the API that we're using. */
-# define LDAP_DEPRECATED 1
-# include <ldap.h>
+# include <winsock2.h>
+# include <sddl.h>
 #endif
 
+
 #include "dirmngr.h"
 #include "misc.h"
-#include "userids.h"
+#include "../common/userids.h"
+#include "../common/mbox-util.h"
+#include "ks-action.h"
 #include "ks-engine.h"
+#include "ldap-misc.h"
 #include "ldap-parse-uri.h"
+#include "ldapserver.h"
 
-#ifndef HAVE_TIMEGM
-time_t timegm(struct tm *tm);
-#endif
-\f
-/* Convert an LDAP error to a GPG error.  */
-static int
-ldap_err_to_gpg_err (int code)
-{
-  gpg_err_code_t ec;
-
-  switch (code)
-    {
-#ifdef LDAP_X_CONNECTING
-    case LDAP_X_CONNECTING: ec = GPG_ERR_LDAP_X_CONNECTING; break;
-#endif
-
-    case LDAP_REFERRAL_LIMIT_EXCEEDED: ec = GPG_ERR_LDAP_REFERRAL_LIMIT; break;
-    case LDAP_CLIENT_LOOP: ec = GPG_ERR_LDAP_CLIENT_LOOP; break;
-    case LDAP_NO_RESULTS_RETURNED: ec = GPG_ERR_LDAP_NO_RESULTS; break;
-    case LDAP_CONTROL_NOT_FOUND: ec = GPG_ERR_LDAP_CONTROL_NOT_FOUND; break;
-    case LDAP_NOT_SUPPORTED: ec = GPG_ERR_LDAP_NOT_SUPPORTED; break;
-    case LDAP_CONNECT_ERROR: ec = GPG_ERR_LDAP_CONNECT; break;
-    case LDAP_NO_MEMORY: ec = GPG_ERR_LDAP_NO_MEMORY; break;
-    case LDAP_PARAM_ERROR: ec = GPG_ERR_LDAP_PARAM; break;
-    case LDAP_USER_CANCELLED: ec = GPG_ERR_LDAP_USER_CANCELLED; break;
-    case LDAP_FILTER_ERROR: ec = GPG_ERR_LDAP_FILTER; break;
-    case LDAP_AUTH_UNKNOWN: ec = GPG_ERR_LDAP_AUTH_UNKNOWN; break;
-    case LDAP_TIMEOUT: ec = GPG_ERR_LDAP_TIMEOUT; break;
-    case LDAP_DECODING_ERROR: ec = GPG_ERR_LDAP_DECODING; break;
-    case LDAP_ENCODING_ERROR: ec = GPG_ERR_LDAP_ENCODING; break;
-    case LDAP_LOCAL_ERROR: ec = GPG_ERR_LDAP_LOCAL; break;
-    case LDAP_SERVER_DOWN: ec = GPG_ERR_LDAP_SERVER_DOWN; break;
-
-    case LDAP_SUCCESS: ec = GPG_ERR_LDAP_SUCCESS; break;
-
-    case LDAP_OPERATIONS_ERROR: ec = GPG_ERR_LDAP_OPERATIONS; break;
-    case LDAP_PROTOCOL_ERROR: ec = GPG_ERR_LDAP_PROTOCOL; break;
-    case LDAP_TIMELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_TIMELIMIT; break;
-    case LDAP_SIZELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_SIZELIMIT; break;
-    case LDAP_COMPARE_FALSE: ec = GPG_ERR_LDAP_COMPARE_FALSE; break;
-    case LDAP_COMPARE_TRUE: ec = GPG_ERR_LDAP_COMPARE_TRUE; break;
-    case LDAP_AUTH_METHOD_NOT_SUPPORTED: ec=GPG_ERR_LDAP_UNSUPPORTED_AUTH;break;
-    case LDAP_STRONG_AUTH_REQUIRED: ec = GPG_ERR_LDAP_STRONG_AUTH_RQRD; break;
-    case LDAP_PARTIAL_RESULTS: ec = GPG_ERR_LDAP_PARTIAL_RESULTS; break;
-    case LDAP_REFERRAL: ec = GPG_ERR_LDAP_REFERRAL; break;
-
-#ifdef LDAP_ADMINLIMIT_EXCEEDED
-    case LDAP_ADMINLIMIT_EXCEEDED: ec = GPG_ERR_LDAP_ADMINLIMIT; break;
-#endif
-
-#ifdef LDAP_UNAVAILABLE_CRITICAL_EXTENSION
-    case LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
-                               ec = GPG_ERR_LDAP_UNAVAIL_CRIT_EXTN; break;
-#endif
-
-    case LDAP_CONFIDENTIALITY_REQUIRED: ec = GPG_ERR_LDAP_CONFIDENT_RQRD; break;
-    case LDAP_SASL_BIND_IN_PROGRESS: ec = GPG_ERR_LDAP_SASL_BIND_INPROG; break;
-    case LDAP_NO_SUCH_ATTRIBUTE: ec = GPG_ERR_LDAP_NO_SUCH_ATTRIBUTE; break;
-    case LDAP_UNDEFINED_TYPE: ec = GPG_ERR_LDAP_UNDEFINED_TYPE; break;
-    case LDAP_INAPPROPRIATE_MATCHING: ec = GPG_ERR_LDAP_BAD_MATCHING; break;
-    case LDAP_CONSTRAINT_VIOLATION: ec = GPG_ERR_LDAP_CONST_VIOLATION; break;
-
-#ifdef LDAP_TYPE_OR_VALUE_EXISTS
-    case LDAP_TYPE_OR_VALUE_EXISTS: ec = GPG_ERR_LDAP_TYPE_VALUE_EXISTS; break;
-#endif
-
-    case LDAP_INVALID_SYNTAX: ec = GPG_ERR_LDAP_INV_SYNTAX; break;
-    case LDAP_NO_SUCH_OBJECT: ec = GPG_ERR_LDAP_NO_SUCH_OBJ; break;
-    case LDAP_ALIAS_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_PROBLEM; break;
-    case LDAP_INVALID_DN_SYNTAX: ec = GPG_ERR_LDAP_INV_DN_SYNTAX; break;
-    case LDAP_IS_LEAF: ec = GPG_ERR_LDAP_IS_LEAF; break;
-    case LDAP_ALIAS_DEREF_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_DEREF; break;
-
-#ifdef LDAP_X_PROXY_AUTHZ_FAILURE
-    case LDAP_X_PROXY_AUTHZ_FAILURE: ec = GPG_ERR_LDAP_X_PROXY_AUTH_FAIL; break;
-#endif
-
-    case LDAP_INAPPROPRIATE_AUTH: ec = GPG_ERR_LDAP_BAD_AUTH; break;
-    case LDAP_INVALID_CREDENTIALS: ec = GPG_ERR_LDAP_INV_CREDENTIALS; break;
 
-#ifdef LDAP_INSUFFICIENT_ACCESS
-    case LDAP_INSUFFICIENT_ACCESS: ec = GPG_ERR_LDAP_INSUFFICIENT_ACC; break;
-#endif
+/* Flags with infos from the connected server.  */
+#define SERVERINFO_REALLDAP 1 /* This is not the PGP keyserver.      */
+#define SERVERINFO_PGPKEYV2 2 /* Needs "pgpKeyV2" instead of "pgpKey"*/
+#define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema.            */
+#define SERVERINFO_NTDS     8 /* Server is an Active Directory.      */
+#define SERVERINFO_GENERIC 16 /* Connected in genric mode.           */
 
-    case LDAP_BUSY: ec = GPG_ERR_LDAP_BUSY; break;
-    case LDAP_UNAVAILABLE: ec = GPG_ERR_LDAP_UNAVAILABLE; break;
-    case LDAP_UNWILLING_TO_PERFORM: ec = GPG_ERR_LDAP_UNWILL_TO_PERFORM; break;
-    case LDAP_LOOP_DETECT: ec = GPG_ERR_LDAP_LOOP_DETECT; break;
-    case LDAP_NAMING_VIOLATION: ec = GPG_ERR_LDAP_NAMING_VIOLATION; break;
-    case LDAP_OBJECT_CLASS_VIOLATION: ec = GPG_ERR_LDAP_OBJ_CLS_VIOLATION; break;
-    case LDAP_NOT_ALLOWED_ON_NONLEAF: ec=GPG_ERR_LDAP_NOT_ALLOW_NONLEAF;break;
-    case LDAP_NOT_ALLOWED_ON_RDN: ec = GPG_ERR_LDAP_NOT_ALLOW_ON_RDN; break;
-    case LDAP_ALREADY_EXISTS: ec = GPG_ERR_LDAP_ALREADY_EXISTS; break;
-    case LDAP_NO_OBJECT_CLASS_MODS: ec = GPG_ERR_LDAP_NO_OBJ_CLASS_MODS; break;
-    case LDAP_RESULTS_TOO_LARGE: ec = GPG_ERR_LDAP_RESULTS_TOO_LARGE; break;
-    case LDAP_AFFECTS_MULTIPLE_DSAS: ec = GPG_ERR_LDAP_AFFECTS_MULT_DSAS; break;
-
-#ifdef LDAP_VLV_ERROR
-    case LDAP_VLV_ERROR: ec = GPG_ERR_LDAP_VLV; break;
-#endif
 
-    case LDAP_OTHER: ec = GPG_ERR_LDAP_OTHER; break;
+/* The page size requested from the server.  */
+#define PAGE_SIZE  100
 
-#ifdef LDAP_CUP_RESOURCES_EXHAUSTED
-    case LDAP_CUP_RESOURCES_EXHAUSTED: ec=GPG_ERR_LDAP_CUP_RESOURCE_LIMIT;break;
-    case LDAP_CUP_SECURITY_VIOLATION: ec=GPG_ERR_LDAP_CUP_SEC_VIOLATION; break;
-    case LDAP_CUP_INVALID_DATA: ec = GPG_ERR_LDAP_CUP_INV_DATA; break;
-    case LDAP_CUP_UNSUPPORTED_SCHEME: ec = GPG_ERR_LDAP_CUP_UNSUP_SCHEME; break;
-    case LDAP_CUP_RELOAD_REQUIRED: ec = GPG_ERR_LDAP_CUP_RELOAD; break;
-#endif
 
-#ifdef LDAP_CANCELLED
-    case LDAP_CANCELLED: ec = GPG_ERR_LDAP_CANCELLED; break;
-#endif
-
-#ifdef LDAP_NO_SUCH_OPERATION
-    case LDAP_NO_SUCH_OPERATION: ec = GPG_ERR_LDAP_NO_SUCH_OPERATION; break;
-#endif
-
-#ifdef LDAP_TOO_LATE
-    case LDAP_TOO_LATE: ec = GPG_ERR_LDAP_TOO_LATE; break;
-#endif
-
-#ifdef LDAP_CANNOT_CANCEL
-    case LDAP_CANNOT_CANCEL: ec = GPG_ERR_LDAP_CANNOT_CANCEL; break;
-#endif
-
-#ifdef LDAP_ASSERTION_FAILED
-    case LDAP_ASSERTION_FAILED: ec = GPG_ERR_LDAP_ASSERTION_FAILED; break;
-#endif
-
-#ifdef LDAP_PROXIED_AUTHORIZATION_DENIED
-    case LDAP_PROXIED_AUTHORIZATION_DENIED:
-                                      ec = GPG_ERR_LDAP_PROX_AUTH_DENIED; break;
-#endif
-
-    default:
-#if defined(LDAP_E_ERROR) && defined(LDAP_X_ERROR)
-      if (LDAP_E_ERROR (code))
-        ec = GPG_ERR_LDAP_E_GENERAL;
-      else if (LDAP_X_ERROR (code))
-        ec = GPG_ERR_LDAP_X_GENERAL;
-      else
+#ifndef HAVE_TIMEGM
+time_t timegm(struct tm *tm);
 #endif
-        ec = GPG_ERR_LDAP_GENERAL;
-      break;
-    }
 
-  return ec;
-}
 
-/* Retrieve an LDAP error and return it's GPG equivalent.  */
-static int
-ldap_to_gpg_err (LDAP *ld)
+/* Object to keep state pertaining to this module.  */
+struct ks_engine_ldap_local_s
 {
-#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
-  int err;
+  LDAP *ldap_conn;
+  LDAPMessage *message;
+  LDAPMessage *msg_iter;  /* Iterator for message.  */
+  unsigned int serverinfo;
+  int scope;
+  char *basedn;
+  char *keyspec;
+  char *filter;
+  struct berval *pagecookie;
+  unsigned int pageno;  /* Current page number (starting at 1). */
+  unsigned int total;   /* Total number of attributes read.     */
+  int more_pages;       /* More pages announced by server.      */
+};
+
+/*-- prototypes --*/
+static char *map_rid_to_dn (ctrl_t ctrl, const char *rid);
+static char *basedn_from_rootdse (ctrl_t ctrl, parsed_uri_t uri);
+
 
-  if (ldap_get_option (ld, LDAP_OPT_ERROR_NUMBER, &err) == 0)
-    return ldap_err_to_gpg_err (err);
-  else
-    return GPG_ERR_GENERAL;
-#elif defined(HAVE_LDAP_LD_ERRNO)
-  return ldap_err_to_gpg_err (ld->ld_errno);
-#else
-  /* We should never get here since the LDAP library should always
-     have either ldap_get_option or ld_errno, but just in case... */
-  return GPG_ERR_INTERNAL;
-#endif
-}
 \f
 static time_t
 ldap2epochtime (const char *timestr)
@@ -284,6 +150,124 @@ epoch2ldaptime (time_t stamp)
     return xstrdup ("INVALID TIME");
 }
 #endif
+
+
+static void
+my_ldap_value_free (char **vals)
+{
+  if (vals)
+    ldap_value_free (vals);
+}
+
+
+/* Print a description of supported variables.  */
+void
+ks_ldap_help_variables (ctrl_t ctrl)
+{
+  const char data[] =
+    "Supported variables in LDAP filter expressions:\n"
+    "\n"
+    "domain           - The defaultNamingContext.\n"
+    "domain_admins    - Group of domain admins.\n"
+    "domain_users     - Group with all user accounts.\n"
+    "domain_guests    - Group with the builtin gues account.\n"
+    "domain_computers - Group with all clients and servers.\n"
+    "cert_publishers  - Group with all cert issuing computers.\n"
+    "protected_users  - Group of users with extra protection.\n"
+    "key_admins       - Group for delegated access to msdsKeyCredentialLink.\n"
+    "enterprise_key_admins     - Similar to key_admins.\n"
+    "domain_domain_controllers - Group with all domain controllers.\n"
+    "sid_domain       - SubAuthority numbers.\n";
+
+  ks_print_help (ctrl, data);
+}
+
+
+/* Helper function for substitute_vars.  */
+static const char *
+getval_for_filter (void *cookie, const char *name)
+{
+  ctrl_t ctrl = cookie;
+  const char *result = NULL;
+
+  if (!strcmp (name, "sid_domain"))
+    {
+#ifdef HAVE_W32_SYSTEM
+      PSID mysid;
+      static char *sidstr;
+      char *s, *s0;
+      int i;
+
+      if (!sidstr)
+        {
+          mysid = w32_get_user_sid ();
+          if (!mysid)
+            {
+              gpg_err_set_errno (ENOENT);
+              goto leave;
+            }
+
+          if (!ConvertSidToStringSid (mysid, &sidstr))
+            {
+              gpg_err_set_errno (EINVAL);
+              goto leave;
+            }
+          /* Example for SIDSTR:
+           * S-1-5-21-3636969917-2569447256-918939550-1127 */
+          for (s0=NULL,s=sidstr,i=0; (s=strchr (s, '-')); i++)
+            {
+              s++;
+              if (i == 3)
+                s0 = s;
+              else if (i==6)
+                {
+                  s[-1] = 0;
+                  break;
+                }
+            }
+          if (!s0)
+            {
+              log_error ("oops: invalid SID received from OS");
+              gpg_err_set_errno (EINVAL);
+              LocalFree (sidstr);
+              goto leave;
+            }
+          sidstr = s0;  /* (We never release SIDSTR thus no memmove.)  */
+        }
+      result = sidstr;
+#else
+      gpg_err_set_errno (ENOSYS);
+      goto leave;
+#endif
+    }
+  else if (!strcmp (name, "domain"))
+    result = basedn_from_rootdse (ctrl, NULL);
+  else if (!strcmp (name, "domain_admins"))
+    result = map_rid_to_dn (ctrl, "512");
+  else if (!strcmp (name, "domain_users"))
+    result = map_rid_to_dn (ctrl, "513");
+  else if (!strcmp (name, "domain_guests"))
+    result = map_rid_to_dn (ctrl, "514");
+  else if (!strcmp (name, "domain_computers"))
+    result = map_rid_to_dn (ctrl, "515");
+  else if (!strcmp (name, "domain_domain_controllers"))
+    result = map_rid_to_dn (ctrl, "516");
+  else if (!strcmp (name, "cert_publishers"))
+    result = map_rid_to_dn (ctrl, "517");
+  else if (!strcmp (name, "protected_users"))
+    result = map_rid_to_dn (ctrl, "525");
+  else if (!strcmp (name, "key_admins"))
+    result = map_rid_to_dn (ctrl, "526");
+  else if (!strcmp (name, "enterprise_key_admins"))
+    result = map_rid_to_dn (ctrl, "527");
+  else
+    result = "";  /* Unknown variables are empty.  */
+
+ leave:
+  return result;
+}
+
+
 \f
 /* Print a help output for the schemata supported by this module. */
 gpg_error_t
@@ -291,39 +275,145 @@ ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri)
 {
   const char data[] =
     "Handler for LDAP URLs:\n"
-    "  ldap://host:port/[BASEDN]???[bindname=BINDNAME,password=PASSWORD]\n"
+    "  ldap://HOST:PORT/[BASEDN]????[bindname=BINDNAME,password=PASSWORD]\n"
     "\n"
     "Note: basedn, bindname and password need to be percent escaped. In\n"
     "particular, spaces need to be replaced with %20 and commas with %2c.\n"
-    "bindname will typically be of the form:\n"
+    "Thus bindname will typically be of the form:\n"
     "\n"
     "  uid=user%2cou=PGP%20Users%2cdc=EXAMPLE%2cdc=ORG\n"
     "\n"
     "The ldaps:// and ldapi:// schemes are also supported.  If ldaps is used\n"
     "then the server's certificate will be checked.  If it is not valid, any\n"
-    "operation will be aborted.\n"
+    "operation will be aborted.  Note that ldaps means LDAP with STARTTLS\n"
+    "\n"
+    "As an alternative to an URL a string in this form may be used:\n"
+    "\n"
+    "  HOST:PORT:BINDNAME:PASSWORD:BASEDN:FLAGS:\n"
+    "\n"
+    "The use of the percent sign or a colon in one of the string values is\n"
+    "currently not supported.\n"
     "\n"
     "Supported methods: search, get, put\n";
   gpg_error_t err;
 
   if(!uri)
     err = ks_print_help (ctrl, "  ldap");
-  else if (strcmp (uri->scheme, "ldap") == 0
-      || strcmp (uri->scheme, "ldaps") == 0
-      || strcmp (uri->scheme, "ldapi") == 0)
+  else if (uri->is_ldap || uri->opaque)
     err = ks_print_help (ctrl, data);
   else
     err = 0;
 
   return err;
 }
+
+
+\f
+/* Create a new empty state object.  Returns NULL on error */
+static struct ks_engine_ldap_local_s *
+ks_ldap_new_state (void)
+{
+  struct ks_engine_ldap_local_s *state;
+
+  state = xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s));
+  if (state)
+    state->scope = LDAP_SCOPE_SUBTREE;
+  return state;
+}
+
+
+/* Clear the state object STATE.  Returns the STATE object.  */
+static struct ks_engine_ldap_local_s *
+ks_ldap_clear_state (struct ks_engine_ldap_local_s *state)
+{
+  if (state->ldap_conn)
+    {
+      ldap_unbind (state->ldap_conn);
+      state->ldap_conn = NULL;
+    }
+  if (state->message)
+    {
+      ldap_msgfree (state->message);
+      state->message = NULL;
+    }
+  if (state->pagecookie)
+    {
+      ber_bvfree (state->pagecookie);
+      state->pagecookie = NULL;
+    }
+  state->serverinfo = 0;
+  xfree (state->basedn);
+  state->scope = LDAP_SCOPE_SUBTREE;
+  state->basedn = NULL;
+  xfree (state->keyspec);
+  state->keyspec = NULL;
+  xfree (state->filter);
+  state->filter = NULL;
+  state->pageno = 0;
+  state->total = 0;
+  state->more_pages = 0;
+  return state;
+}
+
+
+/* Release a state object.  */
+void
+ks_ldap_free_state (struct ks_engine_ldap_local_s *state)
+{
+  if (!state)
+    return;
+  ks_ldap_clear_state (state);
+  xfree (state);
+}
+
+
+/* Helper for ks_ldap_get and ks_ldap_query.  On return first_mode and
+ * next_mode are set accordingly.  */
+static gpg_error_t
+ks_ldap_prepare_my_state (ctrl_t ctrl, unsigned int ks_get_flags,
+                          int *first_mode, int *next_mode)
+{
+  *first_mode = *next_mode = 0;
+
+  if ((ks_get_flags & KS_GET_FLAG_FIRST))
+    {
+      if (ctrl->ks_get_state)
+        ks_ldap_clear_state (ctrl->ks_get_state);
+      else if (!(ctrl->ks_get_state = ks_ldap_new_state ()))
+        return gpg_error_from_syserror ();
+      *first_mode = 1;
+    }
+
+  if ((ks_get_flags & KS_GET_FLAG_NEXT))
+    {
+      if (!ctrl->ks_get_state || !ctrl->ks_get_state->ldap_conn
+          || !ctrl->ks_get_state->message)
+        {
+          log_error ("ks-ldap: --next requested but no state\n");
+          return gpg_error (GPG_ERR_INV_STATE);
+        }
+      *next_mode = 1;
+    }
+
+  /* Do not keep an old state around if not needed.  */
+  if (!(*first_mode || *next_mode))
+    {
+      ks_ldap_free_state (ctrl->ks_get_state);
+      ctrl->ks_get_state = NULL;
+    }
+
+  return 0;
+}
+
+
 \f
 /* Convert a keyspec to a filter.  Return an error if the keyspec is
    bad or is not supported.  The filter is escaped and returned in
    *filter.  It is the caller's responsibility to free *filter.
    *filter is only set if this function returns success (i.e., 0).  */
 static gpg_error_t
-keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact)
+keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact,
+                        unsigned int serverinfo)
 {
   /* Remove search type indicator and adjust PATTERN accordingly.
      Note: don't include a preceding 0x when searching by keyid.  */
@@ -332,6 +422,7 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact)
   KEYDB_SEARCH_DESC desc;
   char *f = NULL;
   char *freeme = NULL;
+  char *p;
 
   gpg_error_t err = classify_user_id (keyspec, &desc, 1);
   if (err)
@@ -351,19 +442,41 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact)
       break;
 
     case KEYDB_SEARCH_MODE_MAIL:
-      if (! only_exact)
-       f = xasprintf ("(pgpUserID=*<%s>*)",
-                      (freeme = ldap_escape_filter (desc.u.name)));
+      freeme = ldap_escape_filter (desc.u.name);
+      if (!freeme)
+        break;
+      if (*freeme == '<' && freeme[1] && freeme[2])
+        {
+          /* Strip angle brackets.  Note that it is does not
+           * matter whether we work on the plan or LDAP escaped
+           * version of the mailbox.  */
+          p = freeme + 1;
+          if (p[strlen(p)-1] == '>')
+            p[strlen(p)-1] = 0;
+        }
+      else
+        p = freeme;
+      if ((serverinfo & SERVERINFO_SCHEMAV2))
+        f = xasprintf ("(&(gpgMailbox=%s)(!(|(pgpRevoked=1)(pgpDisabled=1))))",
+                       p);
+      else if (!only_exact)
+        f = xasprintf ("(pgpUserID=*<%s>*)", p);
       break;
 
     case KEYDB_SEARCH_MODE_MAILSUB:
-      if (! only_exact)
+      if ((serverinfo & SERVERINFO_SCHEMAV2))
+       f = xasprintf("(&(gpgMailbox=*%s*)(!(|(pgpRevoked=1)(pgpDisabled=1))))",
+                      (freeme = ldap_escape_filter (desc.u.name)));
+      else if (!only_exact)
        f = xasprintf ("(pgpUserID=*<*%s*>*)",
                       (freeme = ldap_escape_filter (desc.u.name)));
       break;
 
     case KEYDB_SEARCH_MODE_MAILEND:
-      if (! only_exact)
+      if ((serverinfo & SERVERINFO_SCHEMAV2))
+       f = xasprintf("(&(gpgMailbox=*%s)(!(|(pgpRevoked=1)(pgpDisabled=1))))",
+                      (freeme = ldap_escape_filter (desc.u.name)));
+      else if (!only_exact)
        f = xasprintf ("(pgpUserID=*<*%s>*)",
                       (freeme = ldap_escape_filter (desc.u.name)));
       break;
@@ -376,9 +489,20 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact)
                     (ulong) desc.u.kid[0], (ulong) desc.u.kid[1]);
       break;
 
-    case KEYDB_SEARCH_MODE_FPR16:
-    case KEYDB_SEARCH_MODE_FPR20:
     case KEYDB_SEARCH_MODE_FPR:
+      if ((serverinfo & SERVERINFO_SCHEMAV2))
+        {
+          freeme = bin2hex (desc.u.fpr, desc.fprlen, NULL);
+          if (!freeme)
+            return gpg_error_from_syserror ();
+          f = xasprintf ("(|(gpgFingerprint=%s)(gpgSubFingerprint=%s))",
+                         freeme, freeme);
+          /* FIXME: For an exact search and in case of a match on
+           * gpgSubFingerprint we need to check that there is only one
+           * matching value.  */
+        }
+      break;
+
     case KEYDB_SEARCH_MODE_ISSUER:
     case KEYDB_SEARCH_MODE_ISSUER_SN:
     case KEYDB_SEARCH_MODE_SN:
@@ -405,128 +529,281 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact)
 }
 
 
-\f
-/* Connect to an LDAP server and interrogate it.
 
-     - uri describes the server to connect to and various options
-       including whether to use TLS and the username and password (see
-       ldap_parse_uri for a description of the various fields).
+/* Helper for my_ldap_connect.  */
+static char *
+interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search,
+                     unsigned int *r_serverinfo)
+{
+  int lerr;
+  char **vals;
+  LDAPMessage *si_res;
+  int is_gnupg = 0;
+  char *basedn = NULL;
+  char *attr2[] = { "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL };
+  char *object;
 
-   This function returns:
 
-     - The ldap connection handle in *LDAP_CONNP.
+  object = xasprintf ("cn=pgpServerInfo,%s", basedn_search);
 
-     - The base DN for the PGP key space by querying the
-       pgpBaseKeySpaceDN attribute (This is normally
-       'ou=PGP Keys,dc=EXAMPLE,dc=ORG').
+  npth_unprotect ();
+  lerr = ldap_search_s (ldap_conn, object, LDAP_SCOPE_BASE,
+                        "(objectClass=*)", attr2, 0, &si_res);
+  npth_protect ();
+  xfree (object);
 
-     - The attribute to lookup to find the pgp key.  This is either
-       'pgpKey' or 'pgpKeyV2'.
+  if (lerr == LDAP_SUCCESS)
+    {
+      vals = ldap_get_values (ldap_conn, si_res, "pgpBaseKeySpaceDN");
+      if (vals && vals[0])
+        basedn = xtrystrdup (vals[0]);
+      my_ldap_value_free (vals);
 
-     - Whether this is a real ldap server.  (It's unclear what this
-       exactly means.)
+      vals = ldap_get_values (ldap_conn, si_res, "pgpSoftware");
+      if (vals && vals[0])
+        {
+          if (opt.debug)
+            log_debug ("Server: \t%s\n", vals[0]);
+          if (!ascii_strcasecmp (vals[0], "GnuPG"))
+            is_gnupg = 1;
+        }
+      my_ldap_value_free (vals);
 
-   The values are returned in the passed variables.  If you pass NULL,
-   then the value won't be returned.  It is the caller's
-   responsibility to release *LDAP_CONNP with ldap_unbind and xfree
-   *BASEDNP and *PGPKEYATTRP.
+      vals = ldap_get_values (ldap_conn, si_res, "pgpVersion");
+      if (vals && vals[0])
+        {
+          if (opt.debug)
+            log_debug ("Version:\t%s\n", vals[0]);
+          if (is_gnupg)
+            {
+              const char *fields[2];
+              int nfields;
+              nfields = split_fields (vals[0], fields, DIM(fields));
+              if (nfields > 0 && atoi(fields[0]) > 1)
+                *r_serverinfo |= SERVERINFO_SCHEMAV2;
+              if (nfields > 1
+                  && !ascii_strcasecmp (fields[1], "ntds"))
+                *r_serverinfo |= SERVERINFO_NTDS;
+            }
+        }
+      my_ldap_value_free (vals);
+    }
 
-   If this function successfully interrogated the server, it returns
-   0.  If there was an LDAP error, it returns the LDAP error code.  If
-   an error occurred, *basednp, etc., are undefined (and don't need to
-   be freed.)
+  /* From man ldap_search_s: "res parameter of
+     ldap_search_ext_s() and ldap_search_s() should be
+     freed with ldap_msgfree() regardless of return
+     value of these functions.  */
+  ldap_msgfree (si_res);
+  return basedn;
+}
 
-   If no LDAP error occurred, you still need to check that *basednp is
-   valid.  If it is NULL, then the server does not appear to be an
-   OpenPGP Keyserver.  In this case, you also do not need to xfree
-   *pgpkeyattrp.  */
-static int
-my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
-                 char **basednp, char **pgpkeyattrp, int *real_ldapp)
-{
-  int err = 0;
 
+\f
+/* Connect to an LDAP server and interrogate it.
+ *
+ * URI describes the server to connect to and various options
+ * including whether to use TLS and the username and password (see
+ * ldap_parse_uri for a description of the various fields).  Be
+ * default a PGP keyserver is assumed; if GENERIC is true a generic
+ * ldap conenction is instead established.
+ *
+ * Returns: The ldap connection handle in *LDAP_CONNP, R_BASEDN is set
+ * to the base DN for the PGP key space, several flags will be stored
+ * at SERVERINFO, If you pass NULL, then the value won't be returned.
+ * It is the caller's responsibility to release *LDAP_CONNP with
+ * ldap_unbind and to xfree *BASEDNP.  On error these variables are
+ * cleared.
+ *
+ * Note: On success, you still need to check that *BASEDNP is valid.
+ * If it is NULL, then the server does not appear to be an OpenPGP
+ * keyserver.  */
+static gpg_error_t
+my_ldap_connect (parsed_uri_t uri, unsigned int generic, LDAP **ldap_connp,
+                 char **r_basedn, char **r_host, int *r_use_tls,
+                 unsigned int *r_serverinfo)
+{
+  gpg_error_t err = 0;
+  int lerr;
+  ldap_server_t server = NULL;
   LDAP *ldap_conn = NULL;
+  char *basedn = NULL;
+  char *host = NULL;   /* Host to use.  */
+  int port;            /* Port to use.  */
+  int use_tls;         /* 1 = starttls, 2 = ldap-over-tls  */
+  int use_ntds;        /* Use Active Directory authentication.  */
+  int use_areconly;    /* Lookup only via A record (Windows).  */
+  const char *bindname;
+  const char *password;
+  const char *basedn_arg;
+#ifndef HAVE_W32_SYSTEM
+  char *tmpstr;
+#endif
 
-  char *user = uri->auth;
-  struct uri_tuple_s *password_param = uri_query_lookup (uri, "password");
-  char *password = password_param ? password_param->value : NULL;
+  if (r_basedn)
+    *r_basedn = NULL;
+  if (r_host)
+    *r_host = NULL;
+  if (r_use_tls)
+    *r_use_tls = 0;
+  *r_serverinfo = 0;
 
-  char *basedn = NULL;
-  /* Whether to look for the pgpKey or pgpKeyv2 attribute.  */
-  char *pgpkeyattr = "pgpKey";
-  int real_ldap = 0;
+  if (uri->opaque)
+    {
+      server = ldapserver_parse_one (uri->path, NULL, 0);
+      if (!server)
+        return gpg_error (GPG_ERR_LDAP_OTHER);
+      host = server->host;
+      port = server->port;
+      bindname = server->user;
+      password = bindname? server->pass : NULL;
+      basedn_arg = server->base;
+      use_tls = server->starttls? 1 : server->ldap_over_tls? 2 : 0;
+      use_ntds = server->ntds;
+      use_areconly = server->areconly;
+    }
+  else
+    {
+      host = uri->host;
+      port = uri->port;
+      bindname = uri->auth;
+      password = bindname? uri_query_value (uri, "password") : NULL;
+      basedn_arg = uri->path;
+      use_tls = uri->use_tls ? 1 : 0;
+      use_ntds = uri->ad_current;
+      use_areconly = 0;
+    }
+
+  if (!port)
+    port = use_tls == 2? 636 : 389;
+
+  if (host)
+    {
+      host = xtrystrdup (host);
+      if (!host)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
+    }
 
-  log_debug ("my_ldap_connect(%s:%d/%s????%s%s%s%s%s)\n",
-            uri->host, uri->port,
-            uri->path ?: "",
-            uri->auth ? "bindname=" : "", uri->auth ?: "",
-            uri->auth && password ? "," : "",
-            password ? "password=" : "", password ?: "");
+  if (opt.verbose)
+    log_info ("ldap connect to '%s:%d:%s:%s:%s:%s%s%s'%s\n",
+              host, port,
+              basedn_arg ? basedn_arg : "",
+              bindname ? bindname : "",
+              password ? "*****" : "",
+              use_tls == 1? "starttls" : use_tls == 2? "ldaptls" : "plain",
+              use_ntds ? ",ntds":"",
+              use_areconly? ",areconly":"",
+              generic? " (generic)":"");
 
   /* If the uri specifies a secure connection and we don't support
      TLS, then fail; don't silently revert to an insecure
      connection.  */
-  if (uri->use_tls)
+  if (use_tls)
     {
 #ifndef HAVE_LDAP_START_TLS_S
-      log_error ("Can't use LDAP to connect to the server: no TLS support.");
+      log_error ("ks-ldap: can't connect to the server: no TLS support.");
       err = GPG_ERR_LDAP_NOT_SUPPORTED;
       goto out;
 #endif
     }
 
-  ldap_conn = ldap_init (uri->host, uri->port);
-  if (! ldap_conn)
+
+#ifdef HAVE_W32_SYSTEM
+  /* Note that host==NULL uses the default domain controller.  */
+  npth_unprotect ();
+  ldap_conn = ldap_sslinit (host, port, (use_tls == 2));
+  npth_protect ();
+  if (!ldap_conn)
+    {
+      lerr = LdapGetLastError ();
+      err = ldap_err_to_gpg_err (lerr);
+      log_error ("error initializing LDAP '%s:%d': %s\n",
+                 host, port, ldap_err2string (lerr));
+      goto out;
+    }
+  if (use_areconly)
+    {
+      lerr = ldap_set_option (ldap_conn, LDAP_OPT_AREC_EXCLUSIVE, LDAP_OPT_ON);
+      if (lerr != LDAP_SUCCESS)
+        {
+          log_error ("ks-ldap: unable to set LDAP_OPT_AREC_EXLUSIVE: %s\n",
+                     ldap_err2string (lerr));
+          err = ldap_err_to_gpg_err (lerr);
+          goto out;
+        }
+    }
+
+#else /* Unix */
+  tmpstr = xtryasprintf ("%s://%s:%d",
+                         use_tls == 2? "ldaps" : "ldap",
+                         host, port);
+  if (!tmpstr)
+    {
+      err = gpg_error_from_syserror ();
+      goto out;
+    }
+  npth_unprotect ();
+  lerr = ldap_initialize (&ldap_conn, tmpstr);
+  npth_protect ();
+  if (lerr != LDAP_SUCCESS || !ldap_conn)
     {
-      err = gpg_err_code_from_syserror ();
-      log_error ("Failed to open connection to LDAP server (%s://%s:%d)\n",
-                uri->scheme, uri->host, uri->port);
+      err = ldap_err_to_gpg_err (lerr);
+      log_error ("error initializing LDAP '%s': %s\n",
+                 tmpstr, ldap_err2string (lerr));
+      xfree (tmpstr);
       goto out;
     }
+  xfree (tmpstr);
+#endif /* Unix */
 
 #ifdef HAVE_LDAP_SET_OPTION
   {
     int ver = LDAP_VERSION3;
 
-    err = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver);
-    if (err != LDAP_SUCCESS)
+    lerr = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver);
+    if (lerr != LDAP_SUCCESS)
       {
-       log_error ("gpgkeys: unable to go to LDAP 3: %s\n",
-                  ldap_err2string (err));
+       log_error ("ks-ldap: unable to go to LDAP 3: %s\n",
+                  ldap_err2string (lerr));
+       err = ldap_err_to_gpg_err (lerr);
        goto out;
       }
   }
-#endif
-
-  /* XXX: It would be nice to have an option to provide the server's
-     certificate.  */
-#if 0
-#if defined(LDAP_OPT_X_TLS_CACERTFILE) && defined(HAVE_LDAP_SET_OPTION)
-  err = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE, ca_cert_file);
-  if (err)
+  if (opt.ldaptimeout)
     {
-      log_error ("unable to set ca-cert-file to '%s': %s\n",
-                ca_cert_file, ldap_err2string (err));
-      goto out;
+      int ver = opt.ldaptimeout;
+
+      /* fixme: also use LDAP_OPT_SEND_TIMEOUT?  */
+
+      lerr = ldap_set_option (ldap_conn, LDAP_OPT_TIMELIMIT, &ver);
+      if (lerr != LDAP_SUCCESS)
+        {
+          log_error ("ks-ldap: unable to set LDAP timelimit to %us: %s\n",
+                     opt.ldaptimeout, ldap_err2string (lerr));
+          err = ldap_err_to_gpg_err (lerr);
+          goto out;
+        }
+      if (opt.verbose)
+        log_info ("ldap timeout set to %us\n", opt.ldaptimeout);
     }
-#endif /* LDAP_OPT_X_TLS_CACERTFILE && HAVE_LDAP_SET_OPTION */
 #endif
 
+
 #ifdef HAVE_LDAP_START_TLS_S
-  if (uri->use_tls)
+  if (use_tls == 1)
     {
-      /* XXX: We need an option to determine whether to abort if the
-        certificate is bad or not.  Right now we conservatively
-        default to checking the certificate and aborting.  */
 #ifndef HAVE_W32_SYSTEM
       int check_cert = LDAP_OPT_X_TLS_HARD; /* LDAP_OPT_X_TLS_NEVER */
 
-      err = ldap_set_option (ldap_conn,
-                            LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert);
-      if (err)
+      lerr = ldap_set_option (ldap_conn,
+                              LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert);
+      if (lerr)
        {
-         log_error ("Failed to set TLS option on LDAP connection.\n");
+         log_error ("ldap: error setting an TLS option: %s\n",
+                     ldap_err2string (lerr));
+          err = ldap_err_to_gpg_err (lerr);
          goto out;
        }
 #else
@@ -537,153 +814,168 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
         LDAP_OPT_SERVER_CERTIFICATE, ..); */
 #endif
 
-      err = ldap_start_tls_s (ldap_conn,
+      npth_unprotect ();
+      lerr = ldap_start_tls_s (ldap_conn,
 #ifdef HAVE_W32_SYSTEM
                              /* ServerReturnValue, result */
                              NULL, NULL,
 #endif
                              /* ServerControls, ClientControls */
                              NULL, NULL);
-      if (err)
+      npth_protect ();
+      if (lerr)
        {
-         log_error ("Failed to connect to LDAP server with TLS.\n");
+         log_error ("ldap: error switching to STARTTLS mode: %s\n",
+                     ldap_err2string (lerr));
+          err = ldap_err_to_gpg_err (lerr);
          goto out;
        }
     }
 #endif
 
-  /* By default we don't bind as there is usually no need to.  */
-  if (uri->auth)
+  if (use_ntds)
     {
-      log_debug ("LDAP bind to %s, password %s\n",
-                user, password ? ">not shown<" : ">none<");
-
-      err = ldap_simple_bind_s (ldap_conn, user, password);
-      if (err != LDAP_SUCCESS)
+#ifdef HAVE_W32_SYSTEM
+      npth_unprotect ();
+      lerr = ldap_bind_s (ldap_conn, NULL, NULL, LDAP_AUTH_NEGOTIATE);
+      npth_protect ();
+      if (lerr != LDAP_SUCCESS)
        {
-         log_error ("Internal LDAP bind error: %s\n",
-                    ldap_err2string (err));
+         log_error ("error binding to LDAP via AD: %s\n",
+                     ldap_err2string (lerr));
+          err = ldap_err_to_gpg_err (lerr);
          goto out;
        }
+#else
+      log_error ("ldap: no Active Directory support but 'ntds' requested\n");
+      err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+      goto out;
+#endif
+    }
+  else if (bindname)
+    {
+      npth_unprotect ();
+      lerr = ldap_simple_bind_s (ldap_conn, bindname, password);
+      npth_protect ();
+      if (lerr != LDAP_SUCCESS)
+       {
+         log_error ("error binding to LDAP: %s\n", ldap_err2string (lerr));
+          err = ldap_err_to_gpg_err (lerr);
+         goto out;
+       }
+    }
+  else
+    {
+      /* By default we don't bind as there is usually no need to.  */
     }
 
-  if (uri->path && *uri->path)
-    /* User specified base DN.  */
+  if (generic)
+    {
+      /* Generic use of this function for arbitrary LDAP servers.  */
+      *r_serverinfo |= SERVERINFO_GENERIC;
+      if (basedn_arg && *basedn_arg)
+        {
+          basedn = xtrystrdup (basedn_arg);
+          if (!basedn)
+            {
+              err = gpg_error_from_syserror ();
+              goto out;
+            }
+        }
+    }
+  else if (basedn_arg && *basedn_arg)
     {
-      basedn = xstrdup (uri->path);
+      /* User specified base DN.  In this case we know the server is a
+       * real LDAP server.  */
+      const char *user_basedn = basedn_arg;
+
+      *r_serverinfo |= SERVERINFO_REALLDAP;
 
-      /* If the user specifies a base DN, then we know the server is a
-        real LDAP server.  */
-      real_ldap = 1;
+      /* First try with provided basedn, else retry up one level.
+       * Retry assumes that provided entry is for keyspace,
+       * matching old behavior */
+      basedn = interrogate_ldap_dn (ldap_conn, user_basedn, r_serverinfo);
+      if (!basedn)
+        {
+          const char *basedn_parent = strchr (user_basedn, ',');
+          if (basedn_parent && *basedn_parent)
+            basedn = interrogate_ldap_dn (ldap_conn, basedn_parent + 1,
+                                          r_serverinfo);
+        }
     }
   else
-    {
+    { /* Look for namingContexts.  */
       LDAPMessage *res = NULL;
-      /* Look for namingContexts.  */
       char *attr[] = { "namingContexts", NULL };
 
-      err = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE,
+      npth_unprotect ();
+      lerr = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE,
                           "(objectClass=*)", attr, 0, &res);
-      if (err == LDAP_SUCCESS)
+      npth_protect ();
+
+      if (lerr == LDAP_SUCCESS)
        {
-         char **context = ldap_get_values (ldap_conn, res, "namingContexts");
+         char **context;
+
+          npth_unprotect ();
+          context = ldap_get_values (ldap_conn, res, "namingContexts");
+          npth_protect ();
          if (context)
-           /* We found some, so try each namingContext as the search
-              base and look for pgpBaseKeySpaceDN.  Because we found
-              this, we know we're talking to a regular-ish LDAP
-              server and not an LDAP keyserver.  */
            {
+              /* We found some, so try each namingContext as the
+               * search base and look for pgpBaseKeySpaceDN.  Because
+               * we found this, we know we're talking to a regular-ish
+               * LDAP server and not an LDAP keyserver.  */
              int i;
-             char *attr2[] =
-               { "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL };
 
-             real_ldap = 1;
+              *r_serverinfo |= SERVERINFO_REALLDAP;
 
-             for (i = 0; context[i] && ! basedn; i++)
-               {
-                 char **vals;
-                 LDAPMessage *si_res;
-
-                  {
-                    char *object = xasprintf ("cn=pgpServerInfo,%s",
-                                              context[i]);
-                    err = ldap_search_s (ldap_conn, object, LDAP_SCOPE_BASE,
-                                         "(objectClass=*)", attr2, 0, &si_res);
-                    xfree (object);
-                  }
-
-                 if (err == LDAP_SUCCESS)
-                   {
-                     vals = ldap_get_values (ldap_conn, si_res,
-                                             "pgpBaseKeySpaceDN");
-                     if (vals)
-                       {
-                         basedn = xtrystrdup (vals[0]);
-                         ldap_value_free (vals);
-                       }
-
-                     vals = ldap_get_values (ldap_conn, si_res,
-                                             "pgpSoftware");
-                     if (vals)
-                       {
-                         log_debug ("Server: \t%s\n", vals[0]);
-                         ldap_value_free (vals);
-                       }
-
-                     vals = ldap_get_values (ldap_conn, si_res,
-                                             "pgpVersion");
-                     if (vals)
-                       {
-                         log_debug ("Version:\t%s\n", vals[0]);
-                         ldap_value_free (vals);
-                       }
-                   }
-
-                 /* From man ldap_search_s: "res parameter of
-                    ldap_search_ext_s() and ldap_search_s() should be
-                    freed with ldap_msgfree() regardless of return
-                    value of these functions.  */
-                 ldap_msgfree (si_res);
-               }
+             for (i = 0; context[i] && !basedn; i++)
+                basedn = interrogate_ldap_dn (ldap_conn, context[i],
+                                              r_serverinfo);
 
              ldap_value_free (context);
            }
        }
-      else
+      else /* ldap_search failed.  */
        {
          /* We don't have an answer yet, which means the server might
-            be an LDAP keyserver. */
+            be a PGP.com keyserver. */
          char **vals;
          LDAPMessage *si_res = NULL;
 
          char *attr2[] = { "pgpBaseKeySpaceDN", "version", "software", NULL };
 
-         err = ldap_search_s (ldap_conn, "cn=pgpServerInfo", LDAP_SCOPE_BASE,
+          npth_unprotect ();
+         lerr = ldap_search_s (ldap_conn, "cn=pgpServerInfo", LDAP_SCOPE_BASE,
                               "(objectClass=*)", attr2, 0, &si_res);
-         if (err == LDAP_SUCCESS)
+          npth_protect ();
+         if (lerr == LDAP_SUCCESS)
            {
-             /* For the LDAP keyserver, this is always
-                "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be
-                in the future. */
+             /* For the PGP LDAP keyserver, this is always
+              * "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be
+               * in the future. */
 
              vals = ldap_get_values (ldap_conn, si_res, "baseKeySpaceDN");
-             if (vals)
+             if (vals && vals[0])
                {
                  basedn = xtrystrdup (vals[0]);
-                 ldap_value_free (vals);
                }
+              my_ldap_value_free (vals);
 
              vals = ldap_get_values (ldap_conn, si_res, "software");
-             if (vals)
+             if (vals && vals[0])
                {
-                 log_debug ("ldap: Server: \t%s\n", vals[0]);
-                 ldap_value_free (vals);
+                  if (opt.debug)
+                    log_debug ("ks-ldap: PGP Server: \t%s\n", vals[0]);
                }
+              my_ldap_value_free (vals);
 
              vals = ldap_get_values (ldap_conn, si_res, "version");
-             if (vals)
+             if (vals && vals[0])
                {
-                 log_debug ("ldap: Version:\t%s\n", vals[0]);
+                  if (opt.debug)
+                    log_debug ("ks-ldap: PGP Server Version:\t%s\n", vals[0]);
 
                  /* If the version is high enough, use the new
                     pgpKeyV2 attribute.  This design is iffy at best,
@@ -692,10 +984,10 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
                     keyserver vendor with a different numbering
                     scheme. */
                  if (atoi (vals[0]) > 1)
-                   pgpkeyattr = "pgpKeyV2";
+                    *r_serverinfo |= SERVERINFO_PGPKEYV2;
 
-                 ldap_value_free (vals);
                }
+              my_ldap_value_free (vals);
            }
 
          ldap_msgfree (si_res);
@@ -708,42 +1000,41 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
     }
 
  out:
-  if (! err)
+  if (!err && opt.debug)
     {
       log_debug ("ldap_conn: %p\n", ldap_conn);
-      log_debug ("real_ldap: %d\n", real_ldap);
+      log_debug ("server_type: %s\n",
+                 ((*r_serverinfo & SERVERINFO_GENERIC)
+                  ? "Generic" :
+                  (*r_serverinfo & SERVERINFO_REALLDAP)
+                  ? "LDAP" : "PGP.com keyserver") );
       log_debug ("basedn: %s\n", basedn);
-      log_debug ("pgpkeyattr: %s\n", pgpkeyattr);
+      if (!(*r_serverinfo & SERVERINFO_GENERIC))
+        log_debug ("pgpkeyattr: %s\n",
+                   (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey");
     }
 
-  if (! err && real_ldapp)
-    *real_ldapp = real_ldap;
+  ldapserver_list_free (server);
 
   if (err)
-    xfree (basedn);
+    {
+      xfree (basedn);
+      if (ldap_conn)
+       ldap_unbind (ldap_conn);
+    }
   else
     {
-      if (pgpkeyattrp)
-       {
-         if (basedn)
-           *pgpkeyattrp = xstrdup (pgpkeyattr);
-         else
-           *pgpkeyattrp = NULL;
-       }
-
-      if (basednp)
-       *basednp = basedn;
+      if (r_basedn)
+       *r_basedn = basedn;
       else
        xfree (basedn);
-    }
+      if (r_host)
+        *r_host = host;
+      else
+        xfree (host);
 
-  if (err)
-    {
-      if (ldap_conn)
-       ldap_unbind (ldap_conn);
+      *ldap_connp = ldap_conn;
     }
-  else
-    *ldap_connp = ldap_conn;
 
   return err;
 }
@@ -758,10 +1049,17 @@ extract_keys (estream_t output,
   char **vals;
 
   es_fprintf (output, "INFO %s BEGIN\n", certid);
-  es_fprintf (output, "pub:%s:", certid);
 
-  /* Note: ldap_get_values returns a NULL terminates array of
+  /* Note: ldap_get_values returns a NULL terminated array of
      strings.  */
+
+  vals = ldap_get_values (ldap_conn, message, "gpgfingerprint");
+  if (vals && vals[0] && vals[0][0])
+    es_fprintf (output, "pub:%s:", vals[0]);
+  else
+    es_fprintf (output, "pub:%s:", certid);
+  my_ldap_value_free (vals);
+
   vals = ldap_get_values (ldap_conn, message, "pgpkeytype");
   if (vals && vals[0])
     {
@@ -769,8 +1067,8 @@ extract_keys (estream_t output,
        es_fprintf  (output, "1");
       else if (strcmp (vals[0],"DSS/DH") == 0)
        es_fprintf (output, "17");
-      ldap_value_free (vals);
     }
+  my_ldap_value_free (vals);
 
   es_fprintf (output, ":");
 
@@ -780,8 +1078,8 @@ extract_keys (estream_t output,
       int v = atoi (vals[0]);
       if (v > 0)
        es_fprintf (output, "%d", v);
-      ldap_value_free (vals);
     }
+  my_ldap_value_free (vals);
 
   es_fprintf (output, ":");
 
@@ -790,8 +1088,8 @@ extract_keys (estream_t output,
     {
       if (strlen (vals[0]) == 15)
        es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0]));
-      ldap_value_free (vals);
     }
+  my_ldap_value_free (vals);
 
   es_fprintf (output, ":");
 
@@ -800,211 +1098,743 @@ extract_keys (estream_t output,
     {
       if (strlen (vals[0]) == 15)
        es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0]));
-      ldap_value_free (vals);
+    }
+  my_ldap_value_free (vals);
+
+  es_fprintf (output, ":");
+
+  vals = ldap_get_values (ldap_conn, message, "pgprevoked");
+  if (vals && vals[0])
+    {
+      if (atoi (vals[0]) == 1)
+       es_fprintf (output, "r");
+    }
+  my_ldap_value_free (vals);
+
+  es_fprintf (output, "\n");
+
+  vals = ldap_get_values (ldap_conn, message, "pgpuserid");
+  if (vals && vals[0])
+    {
+      int i;
+      for (i = 0; vals[i]; i++)
+       es_fprintf (output, "uid:%s\n", vals[i]);
+    }
+  my_ldap_value_free (vals);
+
+  vals = ldap_get_values (ldap_conn, message, "modifyTimestamp");
+  if (vals && vals[0])
+    {
+      gnupg_isotime_t atime;
+      if (!rfc4517toisotime (atime, vals[0]))
+        es_fprintf (output, "chg:%s:\n", atime);
+    }
+  my_ldap_value_free (vals);
+
+  es_fprintf (output, "INFO %s END\n", certid);
+}
+
+
+/* For now we do not support LDAP over Tor.  */
+static gpg_error_t
+no_ldap_due_to_tor (ctrl_t ctrl)
+{
+  gpg_error_t err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+  const char *msg = _("LDAP access not possible due to Tor mode");
+
+  log_error ("%s", msg);
+  dirmngr_status_printf (ctrl, "NOTE", "no_ldap_due_to_tor %u %s", err, msg);
+  return gpg_error (GPG_ERR_NOT_SUPPORTED);
+}
+
+
+/* Helper for ks_ldap_get.  Returns 0 if a key was fetched and printed
+ * to FP.  The error code GPG_ERR_NO_DATA is returned if no key was
+ * printed.  Note that FP is updated by this function. */
+static gpg_error_t
+return_one_keyblock (LDAP *ldap_conn, LDAPMessage *msg, unsigned int serverinfo,
+                     estream_t *fp, strlist_t *seenp)
+{
+  gpg_error_t err;
+  char **vals;
+  char **certid;
+
+  /* Use the long keyid to remove duplicates.  The LDAP server returns
+   * the same keyid more than once if there are multiple user IDs on
+   * the key.  Note that this does NOT mean that a keyid that exists
+   * multiple times on the keyserver will not be fetched.  It means
+   * that each KEY, no matter how many user IDs share its keyid, will
+   * be fetched only once.  If a keyid that belongs to more than one
+   * key is fetched, the server quite properly responds with all
+   * matching keys. -ds
+   *
+   * Note that in --first/--next mode we don't do any duplicate
+   * detection.
+   */
+
+  certid = ldap_get_values (ldap_conn, msg, "pgpcertid");
+  if (certid && certid[0])
+    {
+      if (!seenp || !strlist_find (*seenp, certid[0]))
+        {
+          /* It's not a duplicate, add it */
+          if (seenp)
+            add_to_strlist (seenp, certid[0]);
+
+          if (!*fp)
+            {
+              *fp = es_fopenmem(0, "rw");
+              if (!*fp)
+                {
+                  err = gpg_error_from_syserror ();
+                  goto leave;
+                }
+            }
+
+          extract_keys (*fp, ldap_conn, certid[0], msg);
+
+          vals = ldap_get_values (ldap_conn, msg,
+                                  (serverinfo & SERVERINFO_PGPKEYV2)?
+                                  "pgpKeyV2" : "pgpKey");
+          if (!vals)
+            {
+              err = ldap_to_gpg_err (ldap_conn);
+              log_error("ks-ldap: unable to retrieve key %s "
+                        "from keyserver\n", certid[0]);
+            }
+          else
+            {
+              /* We should strip the new lines.  */
+              es_fprintf (*fp, "KEY 0x%s BEGIN\n", certid[0]);
+              es_fputs (vals[0], *fp);
+              es_fprintf (*fp, "\nKEY 0x%s END\n", certid[0]);
+
+              ldap_value_free (vals);
+              err = 0;
+            }
+        }
+      else /* Duplicate.  */
+        err = gpg_error (GPG_ERR_NO_DATA);
+    }
+  else
+    err = gpg_error (GPG_ERR_NO_DATA);
+
+ leave:
+  my_ldap_value_free (certid);
+  return err;
+}
+
+
+/* Helper for ks_ldap_query.  Returns 0 if an attr was fetched and
+ * printed to FP.  The error code GPG_ERR_NO_DATA is returned if no
+ * data was printed.  Note that FP is updated by this function. */
+static gpg_error_t
+return_all_attributes (LDAP *ld, LDAPMessage *msg, estream_t *fp)
+{
+  gpg_error_t err = 0;
+  BerElement *berctx = NULL;
+  char *attr = NULL;
+  const char *attrprefix;
+  struct berval **values = NULL;
+  int idx;
+  int any = 0;
+  const char *s;
+  const char *val;
+  size_t len;
+  char *mydn;
+
+  mydn = ldap_get_dn (ld, msg);
+  if (!*fp)
+    {
+      *fp = es_fopenmem(0, "rw");
+      if (!*fp)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
+
+  /* Always print the DN - note that by using only unbkown attributes
+   * it is pissible to list just the DNs with out addiional
+   * linefeeds.  */
+  es_fprintf (*fp, "Dn: %s\n", mydn? mydn : "[oops DN missing]");
+
+  for (npth_unprotect (), attr = ldap_first_attribute (ld, msg, &berctx),
+         npth_protect ();
+       attr;
+       npth_unprotect (), attr = ldap_next_attribute (ld, msg, berctx),
+         npth_protect ())
+    {
+      npth_unprotect ();
+      values = ldap_get_values_len (ld, msg, attr);
+      npth_protect ();
+
+      if (!values)
+        {
+          if (opt.verbose)
+            log_info ("attribute '%s' not found\n", attr);
+          ldap_memfree (attr);
+          attr = NULL;
+          continue;
+        }
+
+      any = 1;
+
+      if (opt.verbose > 1)
+        {
+          log_info ("found attribute '%s'\n", attr);
+          for (idx=0; values[idx]; idx++)
+            log_info ("         length[%d]=%d\n",
+                      idx, (int)values[0]->bv_len);
+        }
+
+      if (!ascii_strcasecmp (attr, "Dn"))
+        attrprefix = "X-";
+      else if (*attr == '#')
+        attrprefix = "X-hash-";
+      else if (*attr == ' ')
+        attrprefix = "X-blank-";
+      else
+        attrprefix = "";
+      /* FIXME: We should remap all invalid chars in ATTR.   */
+
+      for (idx=0; values[idx]; idx++)
+        {
+          es_fprintf (*fp, "%s%s: ", attrprefix, attr);
+          val = values[idx]->bv_val;
+          len = values[idx]->bv_len;
+          while (len && (s = memchr (val, '\n', len)))
+            {
+              s++; /* We als want to print the LF.  */
+              if (es_fwrite (val, s - val, 1, *fp) != 1)
+                goto fwrite_failed;
+              len -= (s-val);
+              val = s;
+              if (len && es_fwrite (" ", 1, 1, *fp) != 1)
+                goto fwrite_failed;
+            }
+          if (len && es_fwrite (val, len, 1, *fp) != 1)
+            goto fwrite_failed;
+          if (es_fwrite ("\n", 1, 1, *fp) != 1)  /* Final LF.  */
+            goto fwrite_failed;
+        }
+
+      ldap_value_free_len (values);
+      values = NULL;
+      ldap_memfree (attr);
+      attr = NULL;
+    }
+
+  /* One final linefeed to prettify the output.  */
+  if (any && es_fwrite ("\n", 1, 1, *fp) != 1)
+    goto fwrite_failed;
+
+
+ leave:
+  if (values)
+    ldap_value_free_len (values);
+  ldap_memfree (attr);
+  if (mydn)
+    ldap_memfree (mydn);
+  ber_free (berctx, 0);
+  return err;
+
+ fwrite_failed:
+  err = gpg_error_from_syserror ();
+  log_error ("error writing to stdout: %s\n", gpg_strerror (err));
+  goto leave;
+}
+
+
+/* Helper for ks_ldap_get and ks_ldap_query.  Note that KEYSPEC is
+ * only used for diagnostics. */
+static gpg_error_t
+search_and_parse (ctrl_t ctrl, const char *keyspec,
+                  LDAP *ldap_conn, char *basedn, int scope, char *filter,
+                  char **attrs, LDAPMessage **r_message)
+{
+  gpg_error_t err = 0;
+  int l_err, l_reserr;
+  LDAPControl *srvctrls[2] = { NULL, NULL };
+  int count;
+  unsigned int totalcount = 0;
+  LDAPControl *pagectrl = NULL;
+  LDAPControl **resctrls = NULL;
+
+  /* first/next mode is used to retrieve many entries; thus we should
+   * use paged results.  We assume first/next mode if we have a state.
+   * We make the paged mode non-critical so that we get at least as
+   * many entries the server delivers anyway.  */
+  if (ctrl->ks_get_state)
+    {
+      l_err = ldap_create_page_control (ldap_conn, PAGE_SIZE,
+                                        ctrl->ks_get_state->pagecookie, 0,
+                                        &pagectrl);
+      if (err)
+        {
+          err = ldap_err_to_gpg_err (l_err);
+          log_error ("ks-ldap: create_page_control failed: %s\n",
+                     ldap_err2string (l_err));
+          goto leave;
+        }
+
+      ctrl->ks_get_state->more_pages = 0;
+      srvctrls[0] = pagectrl;
+    }
+
+  npth_unprotect ();
+  l_err = ldap_search_ext_s (ldap_conn, basedn, scope,
+                             filter, attrs, 0,
+                             srvctrls[0]? srvctrls : NULL, NULL, NULL, 0,
+                             r_message);
+  npth_protect ();
+  if (l_err)
+    {
+      err = ldap_err_to_gpg_err (l_err);
+      log_error ("ks-ldap: LDAP search error: %s\n", ldap_err2string (l_err));
+      goto leave;
+    }
+
+  if (ctrl->ks_get_state)
+    {
+      l_err = ldap_parse_result (ldap_conn, *r_message, &l_reserr,
+                                 NULL, NULL, NULL, &resctrls, 0);
+      if (l_err)
+        {
+          err = ldap_err_to_gpg_err (l_err);
+          log_error ("ks-ldap: LDAP parse result error: %s\n",
+                     ldap_err2string (l_err));
+          goto leave;
+        }
+      /* Get the current cookie.  */
+      if (ctrl->ks_get_state->pagecookie)
+        {
+          ber_bvfree (ctrl->ks_get_state->pagecookie);
+          ctrl->ks_get_state->pagecookie = NULL;
+        }
+      l_err = ldap_parse_page_control (ldap_conn, resctrls,
+                                       &totalcount,
+                                       &ctrl->ks_get_state->pagecookie);
+      if (l_err)
+        {
+          err = ldap_err_to_gpg_err (l_err);
+          log_error ("ks-ldap: LDAP parse page control error: %s\n",
+                     ldap_err2string (l_err));
+          goto leave;
+        }
+
+      ctrl->ks_get_state->pageno++;
+
+      /* Decide whether there will be more pages.  */
+      ctrl->ks_get_state->more_pages =
+        (ctrl->ks_get_state->pagecookie
+         && ctrl->ks_get_state->pagecookie->bv_val
+         && *ctrl->ks_get_state->pagecookie->bv_val);
+
+      srvctrls[0] = NULL;
+    }
+
+  count = ldap_count_entries (ldap_conn, *r_message);
+  if (ctrl->ks_get_state)
+    {
+      if (count >= 0)
+        ctrl->ks_get_state->total += count;
+      if (opt.verbose)
+        log_info ("ks-ldap: received result page %u%s (%d/%u/%u)\n",
+                  ctrl->ks_get_state->pageno,
+                  ctrl->ks_get_state->more_pages? "":" (last)",
+                  count, ctrl->ks_get_state->total, totalcount);
+    }
+  if (count < 1)
+    {
+      if (!ctrl->ks_get_state || ctrl->ks_get_state->pageno == 1)
+        log_info ("ks-ldap: '%s' not found on LDAP server\n", keyspec);
+
+      if (count == -1)
+        err = ldap_to_gpg_err (ldap_conn);
+      else
+        err = gpg_error (GPG_ERR_NO_DATA);
+
+      goto leave;
+    }
+
+
+ leave:
+  if (resctrls)
+    ldap_controls_free (resctrls);
+  if (pagectrl)
+    ldap_control_free (pagectrl);
+  return err;
+}
+
+
+/* Fetch all entries from the RootDSE and return them as a name value
+ * object.  */
+static nvc_t
+fetch_rootdse (ctrl_t ctrl, parsed_uri_t uri)
+{
+  gpg_error_t err;
+  estream_t infp = NULL;
+  uri_item_t puri;  /* The broken down URI (only one item used).  */
+  nvc_t nvc = NULL;
+
+  /* FIXME: We need the unparsed URI here - use uri_item_t instead
+   * of fix the parser to fill in original */
+  err = ks_action_parse_uri (uri && uri->original? uri->original : "ldap://",
+                             &puri);
+  if (err)
+    return NULL;
+
+  /* Reset authentication for a serverless.  */
+  puri->parsed_uri->ad_current = 0;
+  puri->parsed_uri->auth = NULL;
+
+  if (!strcmp (puri->parsed_uri->scheme, "ldap")
+      || !strcmp (puri->parsed_uri->scheme, "ldaps")
+      || !strcmp (puri->parsed_uri->scheme, "ldapi")
+      || puri->parsed_uri->opaque)
+    {
+      err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_ROOTDSE,
+                           "^&base&(objectclass=*)", NULL, NULL, &infp);
+      if (err)
+        log_error ("ldap: reading the rootDES failed: %s\n",
+                   gpg_strerror (err));
+      else if ((err = nvc_parse (&nvc, NULL, infp)))
+        log_error ("parsing the rootDES failed: %s\n", gpg_strerror (err));
+    }
+
+  es_fclose (infp);
+  release_uri_item_list (puri);
+  if (err)
+    {
+      nvc_release (nvc);
+      nvc = NULL;
+    }
+  return nvc;
+}
+
+
+/* Return the DN for the given RID.  This is used with the Active
+ * Directory.  */
+static char *
+map_rid_to_dn (ctrl_t ctrl, const char *rid)
+{
+  gpg_error_t err;
+  char *result = NULL;
+  estream_t infp = NULL;
+  uri_item_t puri;  /* The broken down URI.  */
+  nvc_t nvc = NULL;
+  char *filter = NULL;
+  const char *s;
+  char *attr[2] = {"dn", NULL};
+
+  err = ks_action_parse_uri ("ldap:///", &puri);
+  if (err)
+    return NULL;
+
+  filter = strconcat ("(objectSid=S-1-5-21-$sid_domain-", rid, ")", NULL);
+  if (!filter)
+    goto leave;
+
+  err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_SUBST,
+                       filter, attr, NULL, &infp);
+  if (err)
+    {
+      log_error ("ldap: AD query '%s' failed: %s\n", filter,gpg_strerror (err));
+      goto leave;
+    }
+  if ((err = nvc_parse (&nvc, NULL, infp)))
+    {
+      log_error ("ldap: parsing the result failed: %s\n",gpg_strerror (err));
+      goto leave;
+    }
+  if (!(s = nvc_get_string (nvc, "Dn:")))
+    {
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      log_error ("ldap: mapping rid '%s'failed: %s\n", rid, gpg_strerror (err));
+      goto leave;
+    }
+  result = xtrystrdup (s);
+  if (!result)
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("ldap: strdup failed: %s\n", gpg_strerror (err));
+      goto leave;
     }
 
-  es_fprintf (output, ":");
+ leave:
+  es_fclose (infp);
+  release_uri_item_list (puri);
+  xfree (filter);
+  nvc_release (nvc);
+  return result;
+}
 
-  vals = ldap_get_values (ldap_conn, message, "pgprevoked");
-  if (vals && vals[0])
-    {
-      if (atoi (vals[0]) == 1)
-       es_fprintf (output, "r");
-      ldap_value_free (vals);
-    }
 
-  es_fprintf (output, "\n");
+/* Return the baseDN for URI which might have already been cached for
+ * this session.  */
+static char *
+basedn_from_rootdse (ctrl_t ctrl, parsed_uri_t uri)
+{
+  const char *s;
 
-  vals = ldap_get_values (ldap_conn, message, "pgpuserid");
-  if (vals && vals[0])
+  if (!ctrl->rootdse && !ctrl->rootdse_tried)
     {
-      int i;
-      for (i = 0; vals[i]; i++)
-       es_fprintf (output, "uid:%s\n", vals[i]);
-      ldap_value_free (vals);
+      ctrl->rootdse = fetch_rootdse (ctrl, uri);
+      ctrl->rootdse_tried = 1;
+      if (ctrl->rootdse)
+        {
+          log_debug ("Dump of all rootDSE attributes:\n");
+          nvc_write (ctrl->rootdse, log_get_stream ());
+          log_debug ("End of dump\n");
+        }
     }
-
-  es_fprintf (output, "INFO %s END\n", certid);
+  s = nvc_get_string (ctrl->rootdse, "defaultNamingContext:");
+  return s? xtrystrdup (s): NULL;
 }
 
+
+
+\f
 /* Get the key described key the KEYSPEC string from the keyserver
  identified by URI.  On success R_FP has an open stream to read the
  data.  */
* identified by URI.  On success R_FP has an open stream to read the
* data.  KS_GET_FLAGS conveys flags from the client.  */
 gpg_error_t
 ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
-            estream_t *r_fp)
+            unsigned int ks_get_flags, gnupg_isotime_t newer, estream_t *r_fp)
 {
-  gpg_error_t err = 0;
-  int ldap_err;
-
+  gpg_error_t err;
+  unsigned int serverinfo;
+  char *host = NULL;
+  int use_tls;
   char *filter = NULL;
-
   LDAP *ldap_conn = NULL;
-
   char *basedn = NULL;
-  char *pgpkeyattr = NULL;
-
+  int scope = LDAP_SCOPE_SUBTREE;
   estream_t fp = NULL;
-
   LDAPMessage *message = NULL;
-
-  (void) ctrl;
-
-  if (opt.use_tor)
+  LDAPMessage *msg;
+  int anykey = 0;
+  int first_mode = 0;
+  int next_mode = 0;
+  int get_first;
+  strlist_t seen = NULL; /* The set of entries that we've seen.  */
+  /* The ordering is significant.  Specifically, "pgpcertid" needs to
+   * be the second item in the list, since everything after it may be
+   * discarded if we aren't in verbose mode.  */
+  char *attrs[] =
     {
-      /* For now we do not support LDAP over Tor.  */
-      log_error (_("LDAP access not possible due to Tor mode\n"));
-      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+     "dummy", /* (to be be replaced.)  */
+     "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled",
+     "pgpkeycreatetime", "modifyTimestamp", "pgpkeysize", "pgpkeytype",
+     "gpgfingerprint",
+     NULL
+    };
+
+  if (dirmngr_use_tor ())
+    {
+      return no_ldap_due_to_tor (ctrl);
     }
 
-  /* Before connecting to the server, make sure we have a sane
-     keyspec.  If not, there is no need to establish a network
-     connection.  */
-  err = keyspec_to_ldap_filter (keyspec, &filter, 1);
+  err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode);
   if (err)
-    return (err);
+    return err;
 
-  /* Make sure we are talking to an OpenPGP LDAP server.  */
-  ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &pgpkeyattr, NULL);
-  if (ldap_err || !basedn)
+  if (next_mode)
     {
-      if (ldap_err)
-       err = ldap_err_to_gpg_err (ldap_err);
+    next_again:
+      if (!ctrl->ks_get_state->msg_iter && ctrl->ks_get_state->more_pages)
+        {
+          /* Get the next page of results.  */
+          if (ctrl->ks_get_state->message)
+            {
+              ldap_msgfree (ctrl->ks_get_state->message);
+              ctrl->ks_get_state->message = NULL;
+            }
+          attrs[0] = ((ctrl->ks_get_state->serverinfo & SERVERINFO_PGPKEYV2)?
+                      "pgpKeyV2" : "pgpKey");
+          err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec,
+                                  ctrl->ks_get_state->ldap_conn,
+                                  ctrl->ks_get_state->basedn,
+                                  ctrl->ks_get_state->scope,
+                                  ctrl->ks_get_state->filter,
+                                  attrs,
+                                  &ctrl->ks_get_state->message);
+          if (err)
+            goto leave;
+          ctrl->ks_get_state->msg_iter = ctrl->ks_get_state->message;
+          get_first = 1;
+        }
       else
-       err = GPG_ERR_GENERAL;
-      goto out;
-    }
+        get_first = 0;
 
-  {
-    /* The ordering is significant.  Specifically, "pgpcertid" needs
-       to be the second item in the list, since everything after it
-       may be discarded we aren't in verbose mode. */
-    char *attrs[] =
-      {
-       pgpkeyattr,
-       "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled",
-       "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype",
-       NULL
-      };
-    /* 1 if we want just attribute types; 0 if we want both attribute
-       types and values.  */
-    int attrsonly = 0;
+      while (ctrl->ks_get_state->msg_iter)
+        {
+          npth_unprotect ();
+          ctrl->ks_get_state->msg_iter
+            = get_first? ldap_first_entry (ctrl->ks_get_state->ldap_conn,
+                                           ctrl->ks_get_state->msg_iter)
+              /*    */ : ldap_next_entry (ctrl->ks_get_state->ldap_conn,
+                                          ctrl->ks_get_state->msg_iter);
+          npth_protect ();
+          get_first = 0;
+          if (ctrl->ks_get_state->msg_iter)
+            {
+              err = return_one_keyblock (ctrl->ks_get_state->ldap_conn,
+                                         ctrl->ks_get_state->msg_iter,
+                                         ctrl->ks_get_state->serverinfo,
+                                         &fp, NULL);
+              if (!err)
+                break;  /* Found.  */
+              else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+                err = 0;  /* Skip empty attributes. */
+              else
+                goto leave;
+            }
+        }
 
-    int count;
+      if (!ctrl->ks_get_state->msg_iter || !fp)
+        {
+          ctrl->ks_get_state->msg_iter = NULL;
+          if (ctrl->ks_get_state->more_pages)
+            goto next_again;
+          err = gpg_error (GPG_ERR_NO_DATA);
+        }
 
-    ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE,
-                             filter, attrs, attrsonly, &message);
-    if (ldap_err)
-      {
-       err = ldap_err_to_gpg_err (ldap_err);
+    }
+  else /* Not in --next mode.  */
+    {
+      /* Make sure we are talking to an OpenPGP LDAP server.  */
+      err = my_ldap_connect (uri, 0, &ldap_conn,
+                             &basedn, &host, &use_tls, &serverinfo);
+      if (err || !basedn)
+        {
+          if (!err)
+            err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
 
-       log_error ("gpgkeys: LDAP search error: %s\n",
-                  ldap_err2string (ldap_err));
-       goto out;
-      }
+      /* Now that we have information about the server we can construct a
+       * query best suited for the capabilities of the server.  */
+      if (first_mode && !*keyspec)
+        {
+          filter = xtrystrdup("(!(|(pgpRevoked=1)(pgpDisabled=1)))");
+          err = filter? 0 : gpg_error_from_syserror ();
+        }
+      else
+        err = keyspec_to_ldap_filter (keyspec, &filter, 1, serverinfo);
+      if (err)
+        goto leave;
 
-    count = ldap_count_entries (ldap_conn, message);
-    if (count < 1)
-      {
-       log_error ("gpgkeys: key %s not found on keyserver\n", keyspec);
+      if (*newer)
+        {
+          char *tstr, *fstr;
+
+          tstr = isotime2rfc4517 (newer);
+          if (!tstr)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          fstr = strconcat ("(&", filter,
+                            "(modifyTimestamp>=", tstr, "))", NULL);
+          xfree (tstr);
+          if (!fstr)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          xfree (filter);
+          filter = fstr;
+        }
 
-       if (count == -1)
-         err = ldap_to_gpg_err (ldap_conn);
-       else
-         err = gpg_error (GPG_ERR_NO_DATA);
+      if (opt.debug)
+        log_debug ("ks-ldap: using filter: %s\n", filter);
 
-       goto out;
-      }
+      /* Replace "dummy".  */
+      attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey";
 
-    {
-      /* There may be more than one unique result for a given keyID,
-        so we should fetch them all (test this by fetching short key
-        id 0xDEADBEEF). */
+      err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, scope,
+                              filter, attrs, &message);
+      if (err)
+        goto leave;
 
-      /* The set of entries that we've seen.  */
-      strlist_t seen = NULL;
-      LDAPMessage *each;
 
-      for (each = ldap_first_entry (ldap_conn, message);
-          each;
-          each = ldap_next_entry (ldap_conn, each))
+      for (npth_unprotect (),
+             msg = ldap_first_entry (ldap_conn, message),
+             npth_protect ();
+          msg;
+           npth_unprotect (),
+             msg = ldap_next_entry (ldap_conn, msg),
+             npth_protect ())
        {
-         char **vals;
-         char **certid;
-
-         /* Use the long keyid to remove duplicates.  The LDAP
-            server returns the same keyid more than once if there
-            are multiple user IDs on the key.  Note that this does
-            NOT mean that a keyid that exists multiple times on the
-            keyserver will not be fetched.  It means that each KEY,
-            no matter how many user IDs share its keyid, will be
-            fetched only once.  If a keyid that belongs to more
-            than one key is fetched, the server quite properly
-            responds with all matching keys. -ds */
-
-         certid = ldap_get_values (ldap_conn, each, "pgpcertid");
-         if (certid && certid[0])
-           {
-             if (! strlist_find (seen, certid[0]))
-               {
-                 /* It's not a duplicate, add it */
-
-                 add_to_strlist (&seen, certid[0]);
-
-                 if (! fp)
-                   fp = es_fopenmem(0, "rw");
-
-                 extract_keys (fp, ldap_conn, certid[0], each);
-
-                 vals = ldap_get_values (ldap_conn, each, pgpkeyattr);
-                 if (! vals)
-                   {
-                     err = ldap_to_gpg_err (ldap_conn);
-                     log_error("gpgkeys: unable to retrieve key %s "
-                               "from keyserver\n", certid[0]);
-                     goto out;
-                   }
-                 else
-                   {
-                     /* We should strip the new lines.  */
-                     es_fprintf (fp, "KEY 0x%s BEGIN\n", certid[0]);
-                     es_fputs (vals[0], fp);
-                     es_fprintf (fp, "\nKEY 0x%s END\n", certid[0]);
-
-                     ldap_value_free (vals);
-                   }
-               }
-           }
-
-         ldap_value_free (certid);
+          err = return_one_keyblock (ldap_conn, msg, serverinfo,
+                                     &fp, first_mode? NULL : &seen);
+          if (!err)
+            {
+              anykey = 1;
+              if (first_mode)
+                break;
+            }
+          else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+            err = 0;  /* Skip empty/duplicate attributes. */
+          else
+            goto leave;
        }
 
-      free_strlist (seen);
+      if (ctrl->ks_get_state) /* Save the iterator.  */
+        ctrl->ks_get_state->msg_iter = msg;
 
-      if (! fp)
+      if (!fp) /* Nothing was found.  */
        err = gpg_error (GPG_ERR_NO_DATA);
+
+      if (!err && anykey)
+        err = dirmngr_status_printf (ctrl, "SOURCE", "%s://%s",
+                                     use_tls? "ldaps" : "ldap",
+                                     host? host:"");
+    }
+
+
+ leave:
+  /* Store our state if needed.  */
+  if (!err && (ks_get_flags & KS_GET_FLAG_FIRST))
+    {
+      log_assert (!ctrl->ks_get_state->ldap_conn);
+      ctrl->ks_get_state->ldap_conn = ldap_conn;
+      ldap_conn = NULL;
+      log_assert (!ctrl->ks_get_state->message);
+      ctrl->ks_get_state->message = message;
+      message = NULL;
+      ctrl->ks_get_state->serverinfo = serverinfo;
+      ctrl->ks_get_state->scope = scope;
+      ctrl->ks_get_state->basedn = basedn;
+      basedn = NULL;
+      ctrl->ks_get_state->keyspec = keyspec? xtrystrdup (keyspec) : NULL;
+      ctrl->ks_get_state->filter = filter;
+      filter = NULL;
+    }
+  if ((ks_get_flags & KS_GET_FLAG_NEXT))
+    {
+      /* Keep the state in --next mode even with errors.  */
+      ldap_conn = NULL;
+      message = NULL;
     }
-  }
 
- out:
   if (message)
     ldap_msgfree (message);
 
   if (err)
-    {
-      if (fp)
-       es_fclose (fp);
-    }
+    es_fclose (fp);
   else
     {
       if (fp)
        es_fseek (fp, 0, SEEK_SET);
-
       *r_fp = fp;
     }
 
-  xfree (pgpkeyattr);
+  free_strlist (seen);
   xfree (basedn);
+  xfree (host);
 
   if (ldap_conn)
     ldap_unbind (ldap_conn);
@@ -1014,6 +1844,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
   return err;
 }
 
+
 /* Search the keyserver identified by URI for keys matching PATTERN.
    On success R_FP has an open stream to read the data.  */
 gpg_error_t
@@ -1022,42 +1853,34 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
 {
   gpg_error_t err;
   int ldap_err;
-
+  unsigned int serverinfo;
   char *filter = NULL;
-
   LDAP *ldap_conn = NULL;
-
   char *basedn = NULL;
-
   estream_t fp = NULL;
 
   (void) ctrl;
 
-  if (opt.use_tor)
+  if (dirmngr_use_tor ())
     {
-      /* For now we do not support LDAP over Tor.  */
-      log_error (_("LDAP access not possible due to Tor mode\n"));
-      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+      return no_ldap_due_to_tor (ctrl);
     }
 
-  /* Before connecting to the server, make sure we have a sane
-     keyspec.  If not, there is no need to establish a network
-     connection.  */
-  err = keyspec_to_ldap_filter (pattern, &filter, 0);
-  if (err)
+  /* Make sure we are talking to an OpenPGP LDAP server.  */
+  err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
+  if (err || !basedn)
     {
-      log_error ("Bad search pattern: '%s'\n", pattern);
-      return (err);
+      if (!err)
+       err = GPG_ERR_GENERAL;
+      goto out;
     }
 
-  /* Make sure we are talking to an OpenPGP LDAP server.  */
-  ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL);
-  if (ldap_err || !basedn)
+  /* Now that we have information about the server we can construct a
+   * query best suited for the capabilities of the server.  */
+  err = keyspec_to_ldap_filter (pattern, &filter, 0, serverinfo);
+  if (err)
     {
-      if (ldap_err)
-       err = ldap_err_to_gpg_err (ldap_err);
-      else
-       err = GPG_ERR_GENERAL;
+      log_error ("Bad search pattern: '%s'\n", pattern);
       goto out;
     }
 
@@ -1080,14 +1903,18 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
     char *attrs[] =
       {
        "pgpcertid", "pgpuserid", "pgprevoked", "pgpdisabled",
-       "pgpkeycreatetime", "pgpkeyexpiretime", "modifytimestamp",
-       "pgpkeysize", "pgpkeytype", NULL
+       "pgpkeycreatetime", "pgpkeyexpiretime", "modifyTimestamp",
+       "pgpkeysize", "pgpkeytype", "gpgfingerprint",
+        NULL
       };
 
-    log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter);
+    if (opt.debug)
+      log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter);
 
+    npth_unprotect ();
     ldap_err = ldap_search_s (ldap_conn, basedn,
                              LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res);
+    npth_protect ();
 
     xfree (filter);
     filter = NULL;
@@ -1097,16 +1924,20 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
        err = ldap_err_to_gpg_err (ldap_err);
 
        log_error ("SEARCH %s FAILED %d\n", pattern, err);
-       log_error ("gpgkeys: LDAP search error: %s\n",
+       log_error ("ks-ldap: LDAP search error: %s\n",
                   ldap_err2string (err));
        goto out;
     }
 
     /* The LDAP server doesn't return a real count of unique keys, so we
        can't use ldap_count_entries here. */
-    for (each = ldap_first_entry (ldap_conn, res);
+    for (npth_unprotect (),
+           each = ldap_first_entry (ldap_conn, res),
+           npth_protect ();
         each;
-        each = ldap_next_entry (ldap_conn, each))
+         npth_unprotect (),
+           each = ldap_next_entry (ldap_conn, each),
+           npth_protect ())
       {
        char **certid = ldap_get_values (ldap_conn, each, "pgpcertid");
        if (certid && certid[0] && ! strlist_find (dupelist, certid[0]))
@@ -1114,15 +1945,16 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
            add_to_strlist (&dupelist, certid[0]);
            count++;
          }
+        my_ldap_value_free (certid);
       }
 
     if (ldap_err == LDAP_SIZELIMIT_EXCEEDED)
       {
        if (count == 1)
-         log_error ("gpgkeys: search results exceeded server limit."
+         log_error ("ks-ldap: search results exceeded server limit."
                     "  First 1 result shown.\n");
        else
-         log_error ("gpgkeys: search results exceeded server limit."
+         log_error ("ks-ldap: search results exceeded server limit."
                     "  First %d results shown.\n", count);
       }
 
@@ -1143,18 +1975,26 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
            LDAPMessage *uids;
 
            certid = ldap_get_values (ldap_conn, each, "pgpcertid");
-           if (! certid || ! certid[0])
-             continue;
+           if (!certid || !certid[0])
+              {
+                my_ldap_value_free (certid);
+                continue;
+              }
 
            /* Have we seen this certid before? */
            if (! strlist_find (dupelist, certid[0]))
              {
                add_to_strlist (&dupelist, certid[0]);
 
-               es_fprintf (fp, "pub:%s:",certid[0]);
+                vals = ldap_get_values (ldap_conn, each, "gpgfingerprint");
+                if (vals && vals[0] && vals[0][0])
+                  es_fprintf (fp, "pub:%s:", vals[0]);
+                else
+                  es_fprintf (fp, "pub:%s:", certid[0]);
+                my_ldap_value_free (vals);
 
                vals = ldap_get_values (ldap_conn, each, "pgpkeytype");
-               if (vals)
+               if (vals && vals[0])
                  {
                    /* The LDAP server doesn't exactly handle this
                       well. */
@@ -1162,74 +2002,72 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
                      es_fputs ("1", fp);
                    else if (strcasecmp (vals[0], "DSS/DH") == 0)
                      es_fputs ("17", fp);
-                   ldap_value_free (vals);
                  }
+               my_ldap_value_free (vals);
 
                es_fputc (':', fp);
 
                vals = ldap_get_values (ldap_conn, each, "pgpkeysize");
-               if (vals)
+               if (vals && vals[0])
                  {
                    /* Not sure why, but some keys are listed with a
                       key size of 0.  Treat that like an unknown. */
                    if (atoi (vals[0]) > 0)
                      es_fprintf (fp, "%d", atoi (vals[0]));
-                   ldap_value_free (vals);
                  }
+                my_ldap_value_free (vals);
 
                es_fputc (':', fp);
 
                /* YYYYMMDDHHmmssZ */
 
                vals = ldap_get_values (ldap_conn, each, "pgpkeycreatetime");
-               if(vals && strlen (vals[0]) == 15)
+               if(vals && vals[0] && strlen (vals[0]) == 15)
                  {
                    es_fprintf (fp, "%u",
                                (unsigned int) ldap2epochtime(vals[0]));
-                   ldap_value_free (vals);
                  }
+                my_ldap_value_free (vals);
 
                es_fputc (':', fp);
 
                vals = ldap_get_values (ldap_conn, each, "pgpkeyexpiretime");
-               if (vals && strlen (vals[0]) == 15)
+               if (vals && vals[0] && strlen (vals[0]) == 15)
                  {
                    es_fprintf (fp, "%u",
                                (unsigned int) ldap2epochtime (vals[0]));
-                   ldap_value_free (vals);
                  }
+                my_ldap_value_free (vals);
 
                es_fputc (':', fp);
 
                vals = ldap_get_values (ldap_conn, each, "pgprevoked");
-               if (vals)
+               if (vals && vals[0])
                  {
                    if (atoi (vals[0]) == 1)
                      es_fprintf (fp, "r");
-                   ldap_value_free (vals);
                  }
+                my_ldap_value_free (vals);
 
                vals = ldap_get_values (ldap_conn, each, "pgpdisabled");
-               if (vals)
+               if (vals && vals[0])
                  {
                    if (atoi (vals[0]) ==1)
                      es_fprintf (fp, "d");
-                   ldap_value_free (vals);
                  }
+                my_ldap_value_free (vals);
 
-#if 0
-               /* This is not yet specified in the keyserver
-                  protocol, but may be someday. */
                es_fputc (':', fp);
 
-               vals = ldap_get_values (ldap_conn, each, "modifytimestamp");
-               if(vals && strlen (vals[0]) == 15)
+               vals = ldap_get_values (ldap_conn, each, "modifyTimestamp");
+               if(vals && vals[0])
                  {
-                   es_fprintf (fp, "%u",
-                               (unsigned int) ldap2epochtime (vals[0]));
-                   ldap_value_free (vals);
+                    gnupg_isotime_t atime;
+                    if (rfc4517toisotime (atime, vals[0]))
+                      *atime = 0;
+                    es_fprintf (fp, "%s", atime);
                  }
-#endif
+                my_ldap_value_free (vals);
 
                es_fprintf (fp, "\n");
 
@@ -1239,10 +2077,13 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
                     uids = ldap_next_entry (ldap_conn, uids))
                  {
                    vals = ldap_get_values (ldap_conn, uids, "pgpcertid");
-                   if (! vals)
-                     continue;
+                    if (!vals || !vals[0])
+                      {
+                        my_ldap_value_free (vals);
+                        continue;
+                      }
 
-                   if (strcasecmp (certid[0], vals[0]) == 0)
+                   if (!ascii_strcasecmp (certid[0], vals[0]))
                      {
                        char **uidvals;
 
@@ -1252,12 +2093,14 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
                                                   uids, "pgpuserid");
                        if (uidvals)
                          {
-                           /* Need to escape any colons */
-                           char *quoted = percent_escape (uidvals[0], NULL);
-                           es_fputs (quoted, fp);
+                           /* Need to percent escape any colons */
+                            char *quoted = try_percent_escape (uidvals[0],
+                                                               NULL);
+                            if (quoted)
+                              es_fputs (quoted, fp);
                            xfree (quoted);
-                           ldap_value_free (uidvals);
                          }
+                        my_ldap_value_free (uidvals);
 
                        es_fprintf (fp, "\n");
                      }
@@ -1266,7 +2109,7 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
                  }
              }
 
-             ldap_value_free (certid);
+            my_ldap_value_free (certid);
          }
       }
 
@@ -1274,13 +2117,13 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
     free_strlist (dupelist);
   }
 
-  log_debug ("SEARCH %s END\n", pattern);
+  if (opt.debug)
+    log_debug ("SEARCH %s END\n", pattern);
 
  out:
   if (err)
     {
-      if (fp)
-       es_fclose (fp);
+      es_fclose (fp);
     }
   else
     {
@@ -1471,7 +2314,7 @@ modlist_dump (LDAPMod **modlist, estream_t output)
          for ((ptr = (*m)->mod_values), (i = 1); ptr && *ptr; ptr++, i ++)
            {
              /* Assuming terminals are about 80 characters wide,
-                display at most most about 10 lines of debugging
+                display at most about 10 lines of debugging
                 output.  If we do trim the buffer, append '...' to
                 the end.  */
              const int max_len = 10 * 70;
@@ -1597,7 +2440,7 @@ uncescape (char *str)
          && hexdigitp (str + r + 3))
        {
          int x = hextobyte (&str[r + 2]);
-         assert (0 <= x && x <= 0xff);
+         log_assert (0 <= x && x <= 0xff);
 
          str[w] = x;
 
@@ -1614,15 +2457,16 @@ uncescape (char *str)
 
 /* Given one line from an info block (`gpg --list-{keys,sigs}
    --with-colons KEYID'), pull it apart and fill in the modlist with
-   the relevant (for the LDAP schema) attributes.  */
+   the relevant (for the LDAP schema) attributes.  EXTRACT_STATE
+   should initally be set to 0 by the caller.  SCHEMAV2 is set if the
+   server supports the version 2 schema.  */
 static void
-extract_attributes (LDAPMod ***modlist, char *line)
+extract_attributes (LDAPMod ***modlist, int *extract_state,
+                    char *line, int schemav2)
 {
   int field_count;
   char **fields;
-
   char *keyid;
-
   int is_pub, is_sub, is_uid, is_sig;
 
   /* Remove trailing whitespace */
@@ -1637,24 +2481,42 @@ extract_attributes (LDAPMod ***modlist, char *line)
   if (field_count < 7)
     goto out;
 
-  is_pub = strcasecmp ("pub", fields[0]) == 0;
-  is_sub = strcasecmp ("sub", fields[0]) == 0;
-  is_uid = strcasecmp ("uid", fields[0]) == 0;
-  is_sig = strcasecmp ("sig", fields[0]) == 0;
+  is_pub = !ascii_strcasecmp ("pub", fields[0]);
+  is_sub = !ascii_strcasecmp ("sub", fields[0]);
+  is_uid = !ascii_strcasecmp ("uid", fields[0]);
+  is_sig = !ascii_strcasecmp ("sig", fields[0]);
+  if (!ascii_strcasecmp ("fpr", fields[0]))
+    {
+      /* Special treatment for a fingerprint.  */
+      if (!(*extract_state & 1))
+        goto out;  /* Stray fingerprint line - ignore.  */
+      *extract_state &= ~1;
+      if (field_count >= 10 && schemav2)
+        {
+          if ((*extract_state & 2))
+            modlist_add (modlist, "gpgFingerprint", fields[9]);
+          else
+            modlist_add (modlist, "gpgSubFingerprint", fields[9]);
+        }
+      goto out;
+    }
+
+  *extract_state &= ~(1|2);
+  if (is_pub)
+    *extract_state |= (1|2);
+  else if (is_sub)
+    *extract_state |= 1;
 
   if (!is_pub && !is_sub && !is_uid && !is_sig)
-    /* Not a relevant line.  */
-    goto out;
+    goto out; /* Not a relevant line.  */
 
   keyid = fields[4];
 
   if (is_uid && strlen (keyid) == 0)
-    /* The uid record type can have an empty keyid.  */
-    ;
+    ; /* The uid record type can have an empty keyid.  */
   else if (strlen (keyid) == 16
           && strspn (keyid, "0123456789aAbBcCdDeEfF") == 16)
-    /* Otherwise, we expect exactly 16 hex characters.  */
-    ;
+    ; /* Otherwise, we expect exactly 16 hex characters.  */
   else
     {
       log_error ("malformed record!\n");
@@ -1694,26 +2556,16 @@ extract_attributes (LDAPMod ***modlist, char *line)
 
   if (is_pub || is_sub)
     {
-      char *size = fields[2];
-      int val = atoi (size);
-      size = NULL;
-
-      if (val > 0)
-       {
-         /* We zero pad this on the left to make PGP happy. */
-         char padded[6];
-         if (val < 99999 && val > 0)
-           {
-             snprintf (padded, sizeof padded, "%05u", val);
-             size = padded;
-           }
-       }
+      char padded[6];
+      int val;
 
-      if (size)
-       {
-         if (is_pub || is_sub)
-           modlist_add (modlist, "pgpKeySize", size);
-       }
+      val = atoi (fields[2]);
+      if (val < 99999 && val > 0)
+        {
+          /* We zero pad this on the left to make PGP happy. */
+          snprintf (padded, sizeof padded, "%05u", val);
+          modlist_add (modlist, "pgpKeySize", padded);
+        }
     }
 
   if (is_pub)
@@ -1736,25 +2588,19 @@ extract_attributes (LDAPMod ***modlist, char *line)
        }
 
       if (algo)
-       {
-         if (is_pub)
-           modlist_add (modlist, "pgpKeyType", algo);
-       }
+        modlist_add (modlist, "pgpKeyType", algo);
     }
 
   if (is_pub || is_sub || is_sig)
     {
       if (is_pub)
        {
-         modlist_add (modlist, "pgpCertID", keyid);
-         modlist_add (modlist, "pgpKeyID", &keyid[8]);
+         modlist_add (modlist, "pgpCertID", keyid);    /* Long keyid(!) */
+         modlist_add (modlist, "pgpKeyID", &keyid[8]); /* Short keyid   */
        }
 
       if (is_sub)
-       modlist_add (modlist, "pgpSubKeyID", keyid);
-
-      if (is_sig)
-       modlist_add (modlist, "pgpSignerID", keyid);
+        modlist_add (modlist, "pgpSubKeyID", keyid);    /* Long keyid(!)  */
     }
 
   if (is_pub)
@@ -1852,25 +2698,22 @@ extract_attributes (LDAPMod ***modlist, char *line)
        }
     }
 
-  if ((is_uid || is_pub) && field_count >= 10)
+  if (is_uid && field_count >= 10)
     {
       char *uid = fields[9];
+      char *mbox;
 
-      if (is_pub && strlen (uid) == 0)
-       /* When using gpg --list-keys, the uid is included.  When
-          passed via gpg, it is not.  It is important to process it
-          when it is present, because gpg 1 won't print a UID record
-          if there is only one key.  */
-       ;
-      else
-       {
-         uncescape (uid);
-         modlist_add (modlist, "pgpUserID", uid);
-       }
+      uncescape (uid);
+      modlist_add (modlist, "pgpUserID", uid);
+      if (schemav2 && (mbox = mailbox_from_userid (uid, 0)))
+        {
+          modlist_add (modlist, "gpgMailbox", mbox);
+          xfree (mbox);
+        }
     }
 
  out:
-  free (fields);
+  xfree (fields);
 }
 
 /* Send the key in {KEY,KEYLEN} with the metadata {INFO,INFOLEN} to
@@ -1883,16 +2726,13 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
 {
   gpg_error_t err = 0;
   int ldap_err;
-
+  unsigned int serverinfo;
   LDAP *ldap_conn = NULL;
   char *basedn = NULL;
-  char *pgpkeyattr = NULL;
-  int real_ldap;
-
   LDAPMod **modlist = NULL;
   LDAPMod **addlist = NULL;
-
   char *data_armored = NULL;
+  int extract_state;
 
   /* The last byte of the info block.  */
   const char *infoend = (const char *) info + infolen - 1;
@@ -1909,40 +2749,44 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
   /* Elide a warning.  */
   (void) ctrl;
 
-  if (opt.use_tor)
+  if (dirmngr_use_tor ())
     {
-      /* For now we do not support LDAP over Tor.  */
-      log_error (_("LDAP access not possible due to Tor mode\n"));
-      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+      return no_ldap_due_to_tor (ctrl);
     }
 
-  ldap_err = my_ldap_connect (uri,
-                              &ldap_conn, &basedn, &pgpkeyattr, &real_ldap);
-  if (ldap_err || !basedn)
+  err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
+  if (err || !basedn)
     {
-      if (ldap_err)
-       err = ldap_err_to_gpg_err (ldap_err);
-      else
+      if (!err)
        err = GPG_ERR_GENERAL;
       goto out;
     }
 
-  if (! real_ldap)
-    /* We appear to have an OpenPGP Keyserver, which can unpack the key
-       on its own (not just a dumb LDAP server).  */
+  if (!(serverinfo & SERVERINFO_REALLDAP))
     {
-      LDAPMod mod, *attrs[2];
-      char *key[] = { data, NULL };
+      /* We appear to have a PGP.com Keyserver, which can unpack the
+       * key on its own (not just a dump LDAP server).  This will
+       * rarely be the case these days.  */
+      LDAPMod mod;
+      LDAPMod *attrs[2];
+      char *key[2];
       char *dn;
 
+      key[0] = data;
+      key[1] = NULL;
       memset (&mod, 0, sizeof (mod));
       mod.mod_op = LDAP_MOD_ADD;
-      mod.mod_type = pgpkeyattr;
+      mod.mod_type = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey";
       mod.mod_values = key;
       attrs[0] = &mod;
       attrs[1] = NULL;
 
-      dn = xasprintf ("pgpCertid=virtual,%s", basedn);
+      dn = xtryasprintf ("pgpCertid=virtual,%s", basedn);
+      if (!dn)
+        {
+          err = gpg_error_from_syserror ();
+          goto out;
+        }
       ldap_err = ldap_add_s (ldap_conn, dn, attrs);
       xfree (dn);
 
@@ -1955,15 +2799,20 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
       goto out;
     }
 
-  modlist = xmalloc (sizeof (LDAPMod *));
+  modlist = xtrymalloc (sizeof (LDAPMod *));
+  if (!modlist)
+    {
+      err = gpg_error_from_syserror ();
+      goto out;
+    }
   *modlist = NULL;
 
   if (dump_modlist)
     {
       dump = es_fopen("/tmp/modlist.txt", "w");
       if (! dump)
-       log_error ("Failed to open /tmp/modlist.txt: %s\n",
-                  strerror (errno));
+       log_error ("failed to open /tmp/modlist.txt: %s\n",
+                  gpg_strerror (gpg_error_from_syserror ()));
 
       if (dump)
        {
@@ -1982,15 +2831,20 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
   modlist_add (&modlist, "pgpKeyType", NULL);
   modlist_add (&modlist, "pgpUserID", NULL);
   modlist_add (&modlist, "pgpKeyCreateTime", NULL);
-  modlist_add (&modlist, "pgpSignerID", NULL);
   modlist_add (&modlist, "pgpRevoked", NULL);
   modlist_add (&modlist, "pgpSubKeyID", NULL);
   modlist_add (&modlist, "pgpKeySize", NULL);
   modlist_add (&modlist, "pgpKeyExpireTime", NULL);
   modlist_add (&modlist, "pgpCertID", NULL);
+  if ((serverinfo & SERVERINFO_SCHEMAV2))
+    {
+      modlist_add (&modlist, "gpgFingerprint", NULL);
+      modlist_add (&modlist, "gpgSubFingerprint", NULL);
+      modlist_add (&modlist, "gpgMailbox", NULL);
+    }
 
   /* Assemble the INFO stuff into LDAP attributes */
-
+  extract_state = 0;
   while (infolen > 0)
     {
       char *temp = NULL;
@@ -2008,17 +2862,18 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
 
       *newline = '\0';
 
-      extract_attributes (&modlist, info);
+      extract_attributes (&addlist, &extract_state, info,
+                          (serverinfo & SERVERINFO_SCHEMAV2));
 
       infolen = infolen - ((uintptr_t) newline - (uintptr_t) info + 1);
       info = newline + 1;
 
       /* Sanity check.  */
       if (! temp)
-       assert ((char *) info + infolen - 1 == infoend);
+       log_assert ((char *) info + infolen - 1 == infoend);
       else
        {
-         assert (infolen == -1);
+         log_assert (infolen == -1);
          xfree (temp);
        }
     }
@@ -2029,7 +2884,9 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
   if (err)
     goto out;
 
-  modlist_add (&addlist, pgpkeyattr, data_armored);
+  modlist_add (&addlist,
+               (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey",
+               data_armored);
 
   /* Now append addlist onto modlist.  */
   modlists_join (&modlist, addlist);
@@ -2052,31 +2909,56 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
      keyserver) this does NOT merge signatures, but replaces the whole
      key.  This should make some people very happy. */
   {
-    char **certid;
+    char **attrval;
     char *dn;
 
-    certid = modlist_lookup (modlist, "pgpCertID");
-    if (/* We should have a value.  */
-       ! certid
-       /* Exactly one.  */
-       || !(certid[0] && !certid[1]))
+    if ((serverinfo & SERVERINFO_NTDS))
       {
-       log_error ("Bad certid.\n");
-       err = GPG_ERR_GENERAL;
-       goto out;
+        /* The modern way using a CN RDN with the fingerprint.  This
+         * has the advantage that we won't have duplicate 64 bit
+         * keyids in the store.  In particular NTDS requires the
+         * DN to be unique.  */
+        attrval = modlist_lookup (addlist, "gpgFingerprint");
+        /* We should have exactly one value.  */
+        if (!attrval || !(attrval[0] && !attrval[1]))
+          {
+            log_error ("ks-ldap: bad gpgFingerprint provided\n");
+            err = GPG_ERR_GENERAL;
+            goto out;
+          }
+        dn = xtryasprintf ("CN=%s,%s", attrval[0], basedn);
       }
+    else  /* The old style way.  */
+      {
+        attrval = modlist_lookup (addlist, "pgpCertID");
+        /* We should have exactly one value.  */
+        if (!attrval || !(attrval[0] && !attrval[1]))
+          {
+            log_error ("ks-ldap: bad pgpCertID provided\n");
+            err = GPG_ERR_GENERAL;
+            goto out;
+          }
+        dn = xtryasprintf ("pgpCertID=%s,%s", attrval[0], basedn);
+      }
+    if (!dn)
+      {
+        err = gpg_error_from_syserror ();
+        goto out;
+      }
+    if (opt.debug)
+      log_debug ("ks-ldap: using DN: %s\n", dn);
 
-    dn = xasprintf ("pgpCertID=%s,%s", certid[0], basedn);
-
+    npth_unprotect ();
     err = ldap_modify_s (ldap_conn, dn, modlist);
     if (err == LDAP_NO_SUCH_OBJECT)
       err = ldap_add_s (ldap_conn, dn, addlist);
+    npth_protect ();
 
     xfree (dn);
 
     if (err != LDAP_SUCCESS)
       {
-       log_error ("gpgkeys: error adding key to keyserver: %s\n",
+       log_error ("ks-ldap: error adding key to keyserver: %s\n",
                   ldap_err2string (err));
        err = ldap_err_to_gpg_err (err);
       }
@@ -2090,7 +2972,6 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
     ldap_unbind (ldap_conn);
 
   xfree (basedn);
-  xfree (pgpkeyattr);
 
   modlist_free (modlist);
   xfree (addlist);
@@ -2099,3 +2980,265 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
 
   return err;
 }
+
+
+\f
+/* Get the data described by FILTER_ARG from URI.  On success R_FP has
+ * an open stream to read the data.  KS_GET_FLAGS conveys flags from
+ * the client.  ATTRS is a NULL terminated list of attributes to
+ * return or NULL for all. */
+gpg_error_t
+ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags,
+               const char *filter_arg, char **attrs,
+               gnupg_isotime_t newer, estream_t *r_fp)
+{
+  gpg_error_t err;
+  unsigned int serverinfo;
+  char *host = NULL;
+  int use_tls;
+  LDAP *ldap_conn = NULL;
+  char *basedn = NULL;
+  estream_t fp = NULL;
+  char *filter_arg_buffer = NULL;
+  char *filter = NULL;
+  int scope = LDAP_SCOPE_SUBTREE;
+  LDAPMessage *message = NULL;
+  LDAPMessage *msg;
+  int anydata = 0;
+  int first_mode = 0;
+  int next_mode = 0;
+  int get_first;
+
+  if (dirmngr_use_tor ())
+    return no_ldap_due_to_tor (ctrl);
+
+  if ((!filter_arg || !*filter_arg) && (ks_get_flags & KS_GET_FLAG_ROOTDSE))
+    filter_arg = "^&base&(objectclass=*)";
+
+  if ((ks_get_flags & KS_GET_FLAG_SUBST)
+      && filter_arg && strchr (filter_arg, '$'))
+    {
+      filter_arg_buffer = substitute_vars (filter_arg, getval_for_filter, ctrl);
+      if (!filter_arg_buffer)
+        {
+          err = gpg_error_from_syserror ();
+          log_error ("substituting filter variables failed: %s\n",
+                     gpg_strerror (err));
+          goto leave;
+        }
+      filter_arg = filter_arg_buffer;
+    }
+
+  err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode);
+  if (err)
+    goto leave;
+
+  if (!next_mode) /* (In --next mode the filter is ignored.)  */
+    {
+      if (!filter_arg || !*filter_arg)
+        {
+          err = gpg_error (GPG_ERR_LDAP_FILTER);
+          goto leave;
+        }
+      err = ldap_parse_extfilter (filter_arg, 0, &basedn, &scope, &filter);
+      if (err)
+        goto leave;
+      if (newer && *newer)
+        {
+          char *tstr, *fstr;
+
+          tstr = isotime2rfc4517 (newer);
+          if (!tstr)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          if (filter && *filter)
+            fstr = strconcat ("(&", filter,
+                              "(modifyTimestamp>=", tstr, "))", NULL);
+          else
+            fstr = strconcat ("(modifyTimestamp>=", tstr, ")", NULL);
+          xfree (tstr);
+          if (!fstr)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          xfree (filter);
+          filter = fstr;
+        }
+    }
+
+
+  if (next_mode)
+    {
+    next_again:
+      if (!ctrl->ks_get_state->msg_iter && ctrl->ks_get_state->more_pages)
+        {
+          /* Get the next page of results.  */
+          if (ctrl->ks_get_state->message)
+            {
+              ldap_msgfree (ctrl->ks_get_state->message);
+              ctrl->ks_get_state->message = NULL;
+            }
+          err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec,
+                                  ctrl->ks_get_state->ldap_conn,
+                                  ctrl->ks_get_state->basedn,
+                                  ctrl->ks_get_state->scope,
+                                  ctrl->ks_get_state->filter,
+                                  attrs,
+                                  &ctrl->ks_get_state->message);
+          if (err)
+            goto leave;
+          ctrl->ks_get_state->msg_iter = ctrl->ks_get_state->message;
+          get_first = 1;
+        }
+      else
+        get_first = 0;
+
+      while (ctrl->ks_get_state->msg_iter)
+        {
+          npth_unprotect ();
+          ctrl->ks_get_state->msg_iter
+            = get_first? ldap_first_entry (ctrl->ks_get_state->ldap_conn,
+                                           ctrl->ks_get_state->msg_iter)
+              /*    */ : ldap_next_entry (ctrl->ks_get_state->ldap_conn,
+                                          ctrl->ks_get_state->msg_iter);
+          npth_protect ();
+          get_first = 0;
+          if (ctrl->ks_get_state->msg_iter)
+            {
+              err = return_all_attributes (ctrl->ks_get_state->ldap_conn,
+                                           ctrl->ks_get_state->msg_iter,
+                                           &fp);
+              if (!err)
+                break;  /* Found.  */
+              else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+                err = 0;  /* Skip empty attributes. */
+              else
+                goto leave;
+            }
+        }
+
+      if (!ctrl->ks_get_state->msg_iter || !fp)
+        {
+          ctrl->ks_get_state->msg_iter = NULL;
+          if (ctrl->ks_get_state->more_pages)
+            goto next_again;
+          err = gpg_error (GPG_ERR_NO_DATA);
+        }
+
+    }
+  else /* Not in --next mode.  */
+    {
+      /* Connect to the LDAP server in generic mode. */
+      char *tmpbasedn;
+
+      err = my_ldap_connect (uri, 1 /*generic*/, &ldap_conn,
+                             &tmpbasedn, &host, &use_tls, &serverinfo);
+      if (err)
+        goto leave;
+      if (basedn)
+        xfree (tmpbasedn); /* Extended syntax overrides.  */
+      else if (tmpbasedn)
+        basedn = tmpbasedn;
+      else if (!(ks_get_flags & KS_GET_FLAG_ROOTDSE))
+        {
+          /* No BaseDN known - get one.  */
+          basedn = basedn_from_rootdse (ctrl, uri);
+        }
+
+      if (opt.debug)
+        {
+          log_debug ("ks-ldap: using basedn: %s\n", basedn);
+          log_debug ("ks-ldap: using filter: %s\n", filter);
+        }
+
+      err = search_and_parse (ctrl, filter, ldap_conn, basedn, scope, filter,
+                              attrs, &message);
+      if (err)
+        goto leave;
+
+
+      for (npth_unprotect (),
+             msg = ldap_first_entry (ldap_conn, message),
+             npth_protect ();
+          msg;
+           npth_unprotect (),
+             msg = ldap_next_entry (ldap_conn, msg),
+             npth_protect ())
+       {
+          err = return_all_attributes (ldap_conn, msg, &fp);
+          if (!err)
+            {
+              anydata = 1;
+              if (first_mode)
+                break;
+            }
+          else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+            err = 0;  /* Skip empty/duplicate attributes. */
+          else
+            goto leave;
+       }
+
+      if (ctrl->ks_get_state) /* Save the iterator.  */
+        ctrl->ks_get_state->msg_iter = msg;
+
+      if (!fp) /* Nothing was found.  */
+       err = gpg_error (GPG_ERR_NO_DATA);
+
+      if (!err && anydata)
+        err = dirmngr_status_printf (ctrl, "SOURCE", "%s://%s",
+                                     use_tls? "ldaps" : "ldap",
+                                     host? host:"");
+    }
+
+
+ leave:
+  /* Store our state if needed.  */
+  if (!err && (ks_get_flags & KS_GET_FLAG_FIRST))
+    {
+      log_assert (!ctrl->ks_get_state->ldap_conn);
+      ctrl->ks_get_state->ldap_conn = ldap_conn;
+      ldap_conn = NULL;
+      log_assert (!ctrl->ks_get_state->message);
+      ctrl->ks_get_state->message = message;
+      message = NULL;
+      ctrl->ks_get_state->serverinfo = serverinfo;
+      ctrl->ks_get_state->scope = scope;
+      ctrl->ks_get_state->basedn = basedn;
+      basedn = NULL;
+      ctrl->ks_get_state->keyspec = filter? xtrystrdup (filter) : NULL;
+      ctrl->ks_get_state->filter = filter;
+      filter = NULL;
+    }
+  if ((ks_get_flags & KS_GET_FLAG_NEXT))
+    {
+      /* Keep the state in --next mode even with errors.  */
+      ldap_conn = NULL;
+      message = NULL;
+    }
+
+  if (message)
+    ldap_msgfree (message);
+
+  if (err)
+    es_fclose (fp);
+  else
+    {
+      if (fp)
+       es_fseek (fp, 0, SEEK_SET);
+      *r_fp = fp;
+    }
+
+  xfree (basedn);
+  xfree (host);
+
+  if (ldap_conn)
+    ldap_unbind (ldap_conn);
+
+  xfree (filter);
+  xfree (filter_arg_buffer);
+
+  return err;
+}