aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2020-12-14 19:28:25 +0100
committerWerner Koch <wk@gnupg.org>2020-12-14 19:29:49 +0100
commite9ddd61fe979b1b8e1a4801f7f916d0222397245 (patch)
tree4acb86b0c643e333e4b3f9ebc10a1277d797dce8
parentcc056eb534c1b8f7d1a90af3b9ecb9d6b2f322fa (diff)
downloadgnupg-e9ddd61fe979b1b8e1a4801f7f916d0222397245.tar.gz
dirmngr: Support the new Active Directory schema
* dirmngr/ks-engine-ldap.c (SERVERINFO_): New constants. (my_ldap_connect): Relace args pgpkeyattrp and real_ldapp by a new serverinfo arg. Set the new info flags. (ks_ldap_get): Adjust for change. (ks_ldap_search): Ditto. (ks_ldap_put): Ditto. Replace xmalloc by xtrymalloc. Change the DN for use with NTDS (aka Active Directory). * doc/ldap/gnupg-ldap-init.ldif (pgpSoftware): Update definition of pgpVersion. * doc/ldap/gnupg-ldap-ad-init.ldif: New. * doc/ldap/gnupg-ldap-ad-schema.ldif: New. -- This is a first take on better Active Directory support. The main change for NTDS in the code is that the an top-RDN of CN is used instead of the old pgpCertID. More changes to come; for example using and storing the fingerprint. Signed-off-by: Werner Koch <wk@gnupg.org>
-rw-r--r--dirmngr/ks-engine-ldap.c247
-rw-r--r--doc/ldap/gnupg-ldap-ad-init.ldif17
-rw-r--r--doc/ldap/gnupg-ldap-ad-schema.ldif353
-rw-r--r--doc/ldap/gnupg-ldap-init.ldif16
4 files changed, 523 insertions, 110 deletions
diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c
index 7d61313d9..e8257fdbc 100644
--- a/dirmngr/ks-engine-ldap.c
+++ b/dirmngr/ks-engine-ldap.c
@@ -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 g10 Code GmbH
*
* This file is part of GnuPG.
*
@@ -49,6 +49,15 @@
#include "ks-engine.h"
#include "ldap-parse-uri.h"
+
+/* Flags with infos from the connected server. */
+#define SERVERINFO_REALLDAP 1 /* This is not the PGP keyserver. */
+#define SERVERINFO_PGPKEYV2 2 /* Needs "pgpeyV2" instead of "pgpKey" */
+#define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */
+#define SERVERINFO_NTDS 8 /* Server is an Active Directory. */
+
+
+
#ifndef HAVE_TIMEGM
time_t timegm(struct tm *tm);
#endif
@@ -427,40 +436,42 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact)
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.
+ *BASEDNP.
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.)
+ R_SERVERINFO receives information about the server.
+
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. */
+ OpenPGP Keyserver. */
static int
my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
- char **basednp, char **pgpkeyattrp, int *real_ldapp)
+ char **basednp, unsigned int *r_serverinfo)
{
int err = 0;
-
LDAP *ldap_conn = NULL;
-
char *user = uri->auth;
- struct uri_tuple_s *password_param = uri_query_lookup (uri, "password");
- char *password = password_param ? password_param->value : NULL;
-
+ struct uri_tuple_s *password_param;
+ char *password;
char *basedn = NULL;
- /* Whether to look for the pgpKey or pgpKeyv2 attribute. */
- char *pgpkeyattr = "pgpKey";
- int real_ldap = 0;
- 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 ?: "");
+ *r_serverinfo = 0;
+
+ password_param = uri_query_lookup (uri, "password");
+ password = password_param ? password_param->value : NULL;
+
+ if (opt.debug)
+ 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 ? ">not shown<": "");
/* If the uri specifies a secure connection and we don't support
TLS, then fail; don't silently revert to an insecure
@@ -490,7 +501,7 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
err = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver);
if (err != LDAP_SUCCESS)
{
- log_error ("gpgkeys: unable to go to LDAP 3: %s\n",
+ log_error ("ks-ldap: unable to go to LDAP 3: %s\n",
ldap_err2string (err));
goto out;
}
@@ -553,8 +564,9 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
/* By default we don't bind as there is usually no need to. */
if (uri->auth)
{
- log_debug ("LDAP bind to %s, password %s\n",
- user, password ? ">not shown<" : ">none<");
+ if (opt.debug)
+ 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)
@@ -566,18 +578,17 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
}
if (uri->path && *uri->path)
- /* User specified base DN. */
{
+ /* User specified base DN. */
basedn = xstrdup (uri->path);
/* If the user specifies a base DN, then we know the server is a
- real LDAP server. */
- real_ldap = 1;
+ * real LDAP server. */
+ *r_serverinfo |= SERVERINFO_REALLDAP;
}
else
- {
+ { /* Look for namingContexts. */
LDAPMessage *res = NULL;
- /* Look for namingContexts. */
char *attr[] = { "namingContexts", NULL };
err = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE,
@@ -586,21 +597,22 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
{
char **context = ldap_get_values (ldap_conn, res, "namingContexts");
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;
+ int is_gnupg = 0;
{
char *object = xasprintf ("cn=pgpServerInfo,%s",
@@ -624,7 +636,10 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
"pgpSoftware");
if (vals)
{
- log_debug ("Server: \t%s\n", vals[0]);
+ if (opt.debug)
+ log_debug ("Server: \t%s\n", vals[0]);
+ if (!ascii_strcasecmp (vals[0], "GnuPG"))
+ is_gnupg = 1;
ldap_value_free (vals);
}
@@ -632,7 +647,20 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
"pgpVersion");
if (vals)
{
- log_debug ("Version:\t%s\n", 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;
+ }
ldap_value_free (vals);
}
}
@@ -650,7 +678,7 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
else
{
/* 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;
@@ -660,9 +688,9 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
"(objectClass=*)", attr2, 0, &si_res);
if (err == 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)
@@ -674,14 +702,16 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
vals = ldap_get_values (ldap_conn, si_res, "software");
if (vals)
{
- log_debug ("ldap: Server: \t%s\n", vals[0]);
+ if (opt.debug)
+ log_debug ("ks-ldap: PGP Server: \t%s\n", vals[0]);
ldap_value_free (vals);
}
vals = ldap_get_values (ldap_conn, si_res, "version");
if (vals)
{
- 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,
@@ -690,7 +720,7 @@ 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);
}
@@ -706,29 +736,20 @@ 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_REALLDAP)
+ ? "LDAP" : "PGP.com keyserver") );
log_debug ("basedn: %s\n", basedn);
- log_debug ("pgpkeyattr: %s\n", pgpkeyattr);
+ log_debug ("pgpkeyattr: %s\n",
+ (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey");
}
- if (! err && real_ldapp)
- *real_ldapp = real_ldap;
-
if (err)
xfree (basedn);
else
{
- if (pgpkeyattrp)
- {
- if (basedn)
- *pgpkeyattrp = xstrdup (pgpkeyattr);
- else
- *pgpkeyattrp = NULL;
- }
-
if (basednp)
*basednp = basedn;
else
@@ -834,16 +855,11 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
{
gpg_error_t err = 0;
int ldap_err;
-
+ unsigned int serverinfo;
char *filter = NULL;
-
LDAP *ldap_conn = NULL;
-
char *basedn = NULL;
- char *pgpkeyattr = NULL;
-
estream_t fp = NULL;
-
LDAPMessage *message = NULL;
(void) ctrl;
@@ -863,7 +879,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
return (err);
/* Make sure we are talking to an OpenPGP LDAP server. */
- ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &pgpkeyattr, NULL);
+ ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
if (ldap_err || !basedn)
{
if (ldap_err)
@@ -879,24 +895,26 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
may be discarded we aren't in verbose mode. */
char *attrs[] =
{
- pgpkeyattr,
+ "dummy",
"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. */
+ * types and values. */
int attrsonly = 0;
-
int count;
+ /* Replace "dummy". */
+ attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey";
+
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);
- log_error ("gpgkeys: LDAP search error: %s\n",
+ log_error ("ks-ldap: LDAP search error: %s\n",
ldap_err2string (ldap_err));
goto out;
}
@@ -904,7 +922,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
count = ldap_count_entries (ldap_conn, message);
if (count < 1)
{
- log_error ("gpgkeys: key %s not found on keyserver\n", keyspec);
+ log_info ("ks-ldap: key %s not found on keyserver\n", keyspec);
if (count == -1)
err = ldap_to_gpg_err (ldap_conn);
@@ -954,11 +972,11 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
extract_keys (fp, ldap_conn, certid[0], each);
- vals = ldap_get_values (ldap_conn, each, pgpkeyattr);
+ vals = ldap_get_values (ldap_conn, each, attrs[0]);
if (! vals)
{
err = ldap_to_gpg_err (ldap_conn);
- log_error("gpgkeys: unable to retrieve key %s "
+ log_error("ks-ldap: unable to retrieve key %s "
"from keyserver\n", certid[0]);
goto out;
}
@@ -1001,7 +1019,6 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
*r_fp = fp;
}
- xfree (pgpkeyattr);
xfree (basedn);
if (ldap_conn)
@@ -1012,6 +1029,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
@@ -1020,13 +1038,10 @@ 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;
@@ -1049,7 +1064,7 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
}
/* Make sure we are talking to an OpenPGP LDAP server. */
- ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL);
+ ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
if (ldap_err || !basedn)
{
if (ldap_err)
@@ -1082,7 +1097,8 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
"pgpkeysize", "pgpkeytype", NULL
};
- log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter);
+ if (opt.debug)
+ log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter);
ldap_err = ldap_search_s (ldap_conn, basedn,
LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res);
@@ -1095,7 +1111,7 @@ 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;
}
@@ -1117,10 +1133,10 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
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);
}
@@ -1272,7 +1288,8 @@ 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)
@@ -1865,15 +1882,11 @@ 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;
/* The last byte of the info block. */
@@ -1898,8 +1911,7 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
- ldap_err = my_ldap_connect (uri,
- &ldap_conn, &basedn, &pgpkeyattr, &real_ldap);
+ ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
if (ldap_err || !basedn)
{
if (ldap_err)
@@ -1909,22 +1921,31 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
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);
@@ -1937,7 +1958,12 @@ 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)
@@ -1996,10 +2022,10 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
/* 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);
}
}
@@ -2010,7 +2036,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);
@@ -2037,17 +2065,25 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
char *dn;
certid = modlist_lookup (addlist, "pgpCertID");
- if (/* We should have a value. */
- ! certid
- /* Exactly one. */
- || !(certid[0] && !certid[1]))
+ /* We should have exactly one value. */
+ if (!certid || !(certid[0] && !certid[1]))
{
- log_error ("Bad certid.\n");
+ log_error ("ks-ldap: bad pgpCertID provided\n");
err = GPG_ERR_GENERAL;
goto out;
}
- dn = xasprintf ("pgpCertID=%s,%s", certid[0], basedn);
+ if ((serverinfo & SERVERINFO_NTDS))
+ dn = xtryasprintf ("CN=%s,%s", certid[0], basedn);
+ else
+ dn = xtryasprintf ("pgpCertID=%s,%s", certid[0], basedn);
+ if (!dn)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+ if (opt.debug)
+ log_debug ("ks-ldap: using DN: %s\n", dn);
err = ldap_modify_s (ldap_conn, dn, modlist);
if (err == LDAP_NO_SUCH_OBJECT)
@@ -2057,7 +2093,7 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
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);
}
@@ -2071,7 +2107,6 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
ldap_unbind (ldap_conn);
xfree (basedn);
- xfree (pgpkeyattr);
modlist_free (modlist);
xfree (addlist);
diff --git a/doc/ldap/gnupg-ldap-ad-init.ldif b/doc/ldap/gnupg-ldap-ad-init.ldif
new file mode 100644
index 000000000..f9de238d4
--- /dev/null
+++ b/doc/ldap/gnupg-ldap-ad-init.ldif
@@ -0,0 +1,17 @@
+# gnupg-ldap-ad-init.ldif -*- conf -*-
+#
+# Entries connecting the schema specified in gnupg-ldap-ad-schema.ldif.
+# Revision: 2020-12-08
+
+dn: cn=GnuPG Keys,dc=w32demo,dc=g10code,dc=de
+changetype: add
+objectClass: container
+cn: GnuPG Keys
+
+dn: cn=PGPServerInfo,dc=w32demo,dc=g10code,dc=de
+changetype: add
+objectClass: pgpServerInfo
+cn: PGPServerInfo
+pgpBaseKeySpaceDN: cn=GnuPG Keys,dc=w32demo,dc=g10code,dc=de
+pgpSoftware: GnuPG
+pgpVersion: 2 ntds
diff --git a/doc/ldap/gnupg-ldap-ad-schema.ldif b/doc/ldap/gnupg-ldap-ad-schema.ldif
new file mode 100644
index 000000000..6c4d19f15
--- /dev/null
+++ b/doc/ldap/gnupg-ldap-ad-schema.ldif
@@ -0,0 +1,353 @@
+# gnupg-ldap-scheme.ldif -*- conf -*-
+#
+# Schema for an OpenPGP LDAP keyserver. This is a slighly enhanced
+# version of the original LDAP schema used for PGP keyservers as
+# installed at quite some sites.
+# Revision: 2020-12-08
+
+# Some notes:
+# - Backup your AD! It is not possible to revert changes of the schema.
+# - Try it first on a test system.
+# - To import the new attributes and classes use:
+# ldifde -i -vv -f gnupg-ldap-ad-schema.ldif
+# -c "DC=EXAMPLEDC" "DC=example,DC=org"
+# (the above command is given as one line)
+# - The schema does not get its own distingished name as done with OpenLDAP.
+# - The first GUID we use is f406e7a5-a5ea-411e-9ddd-2e4e66899800
+# and incremented for each attribute.
+#
+# - Some OIDs, oMSyntax, and original OIDs:
+# 2.5.5.1 (127) Object (DS-DN) (1.3.6.1.4.1.1466.115.121.1.12)
+# 2.5.5.3 (27) Case-sensitive string
+# 2.5.5.9 (2) 32 bit signed integer
+# 2.5.5.10 (4) Octet string (1.3.6.1.4.1.1466.115.121.1.26)
+# 2.5.5.11 (23) UTC-Time string
+# 2.5.5.12 (64) Case-insensitive Unicode string
+# 2.5.5.12 (64) Directory String in UTF-8 (1.3.6.1.4.1.1466.115.121.1.15)
+# 2.5.5.16 (65) 64 bit signed integer
+
+# The base DN for the PGP key space by querying the
+# pgpBaseKeySpaceDN attribute (This is normally
+# 'ou=PGP Keys,dc=example,dc=com').
+dn: CN=pgpBaseKeySpaceDN,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.8
+lDAPDisplayName: pgpBaseKeySpaceDN
+description: Points to DN of the object that will store the PGP keys.
+attributeSyntax: 2.5.5.1
+oMSyntax: 127
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAA==
+
+# See gnupg-ldap-init.ldif for a description of this attribute
+dn: CN=pgpSoftware,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.9
+lDAPDisplayName: pgpSoftware
+description: 'Origin of the GnuPG keyserver schema'
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAQ==
+
+# See gnupg-ldap-init.ldif for a description of this attribute
+dn: CN=pgpVersion,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.10
+lDAPDisplayName: pgpVersion
+description: Version of this schema
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAg==
+
+
+# The attribute holding the OpenPGP keyblock.
+# The legacy PGP LDAP server used pgpKeyV2 instead.
+dn: CN=pgpKey,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.11
+lDAPDisplayName: pgpKey
+description: OpenPGP public key block
+attributeSyntax: 2.5.5.10
+oMSyntax: 4
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAw==
+
+# The long key-ID
+dn: CN=pgpCertID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.12
+lDAPDisplayName: pgpCertID
+description: OpenPGP long key id
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBA==
+
+# A flag to temporary disable a keyblock
+dn: CN=pgpDisabled,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.13
+lDAPDisplayName: pgpDisabled
+description: pgpDisabled attribute for PGP
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBQ==
+
+# The short key id. This is actually not required and should thus not
+# be used by client software.
+dn: CN=pgpKeyID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.14
+lDAPDisplayName: pgpKeyID
+description: OpenPGP short key id
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBg==
+
+# The algorithm of the key. Used to be "RSA" or "DSS/DH".
+dn: CN=pgpKeyType,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.15
+lDAPDisplayName: pgpKeyType
+description: pgpKeyType attribute for PGP
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBw==
+
+# The User-ID. GnuPG maps its user-ID classes this way:
+# exact: (pgpUserID=%s)
+# substr: (pgpUserID=*%s*)
+# mail: (pgpUserID=*<%s>*)
+# mailsub: (pgpUserID=*<*%s*>*)
+# mailend: (pgpUserID=*<*%s>*)
+dn: CN=pgpUserID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.16
+lDAPDisplayName: pgpUserID
+description: User ID(s) associated with the key
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: FALSE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCA==
+
+# The creation time of the primary key.
+# Stored in ISO format: "20201231 120000"
+dn: CN=pgpKeyCreateTime,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.17
+lDAPDisplayName: pgpKeyCreateTime
+description: Primary key creation time
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCQ==
+
+# SignerIDs are not used
+dn: CN=pgpSignerID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.18
+lDAPDisplayName: pgpSignerID
+description: pgpSignerID attribute for PGP
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: FALSE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCg==
+
+# A value of 1 indicates that the keyblock has been revoked
+dn: CN=pgpRevoked,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.19
+lDAPDisplayName: pgpRevoked
+description: pgpRevoked attribute for PGP
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCw==
+
+# The Subkey key ids
+dn: CN=pgpSubKeyID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.20
+lDAPDisplayName: pgpSubKeyID
+description: Sub-key ID(s) of the PGP key
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: FALSE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDA==
+
+# A hint on the keysize.
+dn: CN=pgpKeySize,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.21
+lDAPDisplayName: pgpKeySize
+description: pgpKeySize attribute for PGP
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: FALSE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDQ==
+
+# Expiration time of the primary key.
+# Stored in ISO format: "20201231 120000"
+dn: CN=pgpKeyExpireTime,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.3401.8.2.22
+lDAPDisplayName: pgpKeyExpireTime
+description: pgpKeyExpireTime attribute for PGP
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDg==
+
+# The hex encoded fingerprint of the primary key.
+dn: CN=gpgFingerprint,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.11591.2.4.1.1
+lDAPDisplayName: gpgFingerprint
+description: Fingerprint of the primary key
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: TRUE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDw==
+
+# A list of hex encoded fingerprints of the subkeys.
+dn: CN=gpgSubFingerprint,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.11591.2.4.1.2
+lDAPDisplayName: gpgSubFingerprint
+description: Fingerprints of the secondary keys
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: FALSE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYEA==
+
+# A list of utf8 encoded addr-spec used instead of mail/rfc822Mailbox
+dn: CN=gpgMailbox,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.11591.2.4.1.3
+lDAPDisplayName: gpgMailbox
+description: The utf8 encoded addr-spec of a mailbox
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: FALSE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYEQ==
+
+# A list of hex encoded long keyids of all subkeys.
+dn: CN=gpgSubCertID,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: attributeSchema
+attributeID: 1.3.6.1.4.1.11591.2.4.1.4
+lDAPDisplayName: gpgSubCertID
+description: OpenPGP long subkey id
+attributeSyntax: 2.5.5.12
+oMSyntax: 64
+isSingleValued: FALSE
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYEg==
+
+# Unused GUIDs:
+# 9AbnpaXqQR6d3S5OZomYEw==
+# 9AbnpaXqQR6d3S5OZomYFA==
+# 9AbnpaXqQR6d3S5OZomYFQ==
+# 9AbnpaXqQR6d3S5OZomYFg==
+# 9AbnpaXqQR6d3S5OZomYFw==
+# 9AbnpaXqQR6d3S5OZomYGA==
+# 9AbnpaXqQR6d3S5OZomYGQ==
+# 9AbnpaXqQR6d3S5OZomYGg==
+# 9AbnpaXqQR6d3S5OZomYGw==
+# 9AbnpaXqQR6d3S5OZomYHA==
+# 9AbnpaXqQR6d3S5OZomYHQ==
+# 9AbnpaXqQR6d3S5OZomYHg==
+# 9AbnpaXqQR6d3S5OZomYHw==
+
+
+# Sync the schema cache
+DN:
+changetype: modify
+add: schemaUpdateNow
+schemaUpdateNow: 1
+-
+
+
+#
+# Used by regular LDAP servers to indicate pgp support.
+# (structural class)
+#
+dn: CN=pgpServerInfo,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: classSchema
+governsID: 1.3.6.1.4.1.3401.8.2.23
+lDAPDisplayName: pgpServerInfo
+description: An OpenPGP public keyblock store
+subClassOf: top
+objectClassCategory: 1
+mustContain: cn
+mustContain: pgpBaseKeySpaceDN
+mayContain: pgpSoftware
+mayContain: pgpVersion
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYIA==
+
+# The original PGP key object extended with a few extra attributes.
+# All new software should set them but this is not enforced for
+# backward compatibility of client software.
+# (structural class, writable)
+dn: CN=pgpKeyInfo,CN=Schema,CN=Configuration,DC=EXAMPLEDC
+changetype: ntdsSchemaAdd
+objectClass: classSchema
+governsID: 1.3.6.1.4.1.3401.8.2.24
+lDAPDisplayName: pgpKeyInfo
+description: An OpenPGP public keyblock
+subClassOf: top
+objectClassCategory: 1
+instanceType: 4
+mustContain: pgpCertID
+mustContain: pgpKey
+mayContain: pgpDisabled
+mayContain: pgpKeyID
+mayContain: pgpKeyType
+mayContain: pgpUserID
+mayContain: pgpKeyCreateTime
+mayContain: pgpSignerID
+mayContain: pgpRevoked
+mayContain: pgpSubKeyID
+mayContain: pgpKeySize
+mayContain: pgpKeyExpireTime
+mayContain: gpgFingerprint
+mayContain: gpgSubFingerprint
+mayContain: gpgSubCertID
+mayContain: gpgMailbox
+schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYIQ==
+
+
+# Sync the schema cache
+DN:
+changetype: modify
+add: schemaUpdateNow
+schemaUpdateNow: 1
+-
+
+
+#
+# end-of-file
+#
diff --git a/doc/ldap/gnupg-ldap-init.ldif b/doc/ldap/gnupg-ldap-init.ldif
index f184f9ee2..8f62c5c61 100644
--- a/doc/ldap/gnupg-ldap-init.ldif
+++ b/doc/ldap/gnupg-ldap-init.ldif
@@ -12,10 +12,18 @@ pgpBaseKeySpaceDN: ou=GnuPG Keys,dc=example,dc=com
# Using the value GnuPG here indicates that pgpVersion below has a
# well-defined meaning.
pgpSoftware: GnuPG
-# Currently used values:
-# 1 :: Classic PGP schema
-# 2 :: The attributes gpgFingerprint, gpgSubFingerprint,
-# gpgSubCertID, and gpgMailbox are part of the schema.
+# pgpVersion is a string with space delimited items:
+#
+# Item 1 - Implemented schema version. This is an integer with one
+# of these values:
+# 1 = Classic PGP schema (default)
+# 2 = The attributes gpgFingerprint, gpgSubFingerprint,
+# gpgSubCertID, and gpgMailbox are part of the schema.
+# Item 2 - A string with the used LDAP server
+# "-" = Unknown (default)
+# "ntds" = Windows Directory Service (AD DS)
+# "openldap" = OpenLDAP
+#
pgpVersion: 2
dn: ou=GnuPG Keys,dc=example,dc=com