summaryrefslogtreecommitdiffstats
path: root/dirmngr/ks-engine-ldap.c
diff options
context:
space:
mode:
Diffstat (limited to 'dirmngr/ks-engine-ldap.c')
-rw-r--r--dirmngr/ks-engine-ldap.c2544
1 files changed, 2544 insertions, 0 deletions
diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c
new file mode 100644
index 0000000..22f974c
--- /dev/null
+++ b/dirmngr/ks-engine-ldap.c
@@ -0,0 +1,2544 @@
+/* ks-engine-ldap.c - talk to a LDAP keyserver
+ * Copyright (C) 2001, 2002, 2004, 2005, 2006
+ * 2007 Free Software Foundation, Inc.
+ * Copyright (C) 2015, 2020 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef HAVE_GETOPT_H
+# include <getopt.h>
+#endif
+#include <stdlib.h>
+#include <npth.h>
+
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "../common/userids.h"
+#include "../common/mbox-util.h"
+#include "ks-engine.h"
+#include "ldap-misc.h"
+#include "ldap-parse-uri.h"
+#include "ldapserver.h"
+
+
+/* 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. */
+
+
+/* The page size requested from the server. */
+#define PAGE_SIZE 100
+
+
+#ifndef HAVE_TIMEGM
+time_t timegm(struct tm *tm);
+#endif
+
+
+/* Object to keep state pertaining to this module. */
+struct ks_engine_ldap_local_s
+{
+ LDAP *ldap_conn;
+ LDAPMessage *message;
+ LDAPMessage *msg_iter; /* Iterator for message. */
+ unsigned int serverinfo;
+ 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. */
+};
+
+
+
+
+static time_t
+ldap2epochtime (const char *timestr)
+{
+ struct tm pgptime;
+ time_t answer;
+
+ memset (&pgptime, 0, sizeof(pgptime));
+
+ /* YYYYMMDDHHmmssZ */
+
+ sscanf (timestr, "%4d%2d%2d%2d%2d%2d",
+ &pgptime.tm_year,
+ &pgptime.tm_mon,
+ &pgptime.tm_mday,
+ &pgptime.tm_hour,
+ &pgptime.tm_min,
+ &pgptime.tm_sec);
+
+ pgptime.tm_year -= 1900;
+ pgptime.tm_isdst = -1;
+ pgptime.tm_mon--;
+
+ /* mktime() takes the timezone into account, so we use timegm() */
+
+ answer = timegm (&pgptime);
+
+ return answer;
+}
+
+/* Caller must free the result. */
+static char *
+tm2ldaptime (struct tm *tm)
+{
+ struct tm tmp = *tm;
+ char buf[16];
+
+ /* YYYYMMDDHHmmssZ */
+
+ tmp.tm_year += 1900;
+ tmp.tm_mon ++;
+
+ snprintf (buf, sizeof buf, "%04d%02d%02d%02d%02d%02dZ",
+ tmp.tm_year,
+ tmp.tm_mon,
+ tmp.tm_mday,
+ tmp.tm_hour,
+ tmp.tm_min,
+ tmp.tm_sec);
+
+ return xstrdup (buf);
+}
+
+#if 0
+/* Caller must free */
+static char *
+epoch2ldaptime (time_t stamp)
+{
+ struct tm tm;
+ if (gmtime_r (&stamp, &tm))
+ return tm2ldaptime (&tm);
+ else
+ return xstrdup ("INVALID TIME");
+}
+#endif
+
+
+static void
+my_ldap_value_free (char **vals)
+{
+ if (vals)
+ ldap_value_free (vals);
+}
+
+
+
+/* Print a help output for the schemata supported by this module. */
+gpg_error_t
+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"
+ "\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"
+ "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. 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")
+ || !strcmp (uri->scheme, "ldaps")
+ || !strcmp (uri->scheme, "ldapi")
+ || uri->opaque)
+ err = ks_print_help (ctrl, data);
+ else
+ err = 0;
+
+ return err;
+}
+
+/* Create a new empty state object. Returns NULL on error */
+static struct ks_engine_ldap_local_s *
+ks_ldap_new_state (void)
+{
+ return xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s));
+}
+
+
+/* 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->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);
+}
+
+
+
+/* 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,
+ unsigned int serverinfo)
+{
+ /* Remove search type indicator and adjust PATTERN accordingly.
+ Note: don't include a preceding 0x when searching by keyid. */
+
+ /* XXX: Should we include disabled / revoke options? */
+ KEYDB_SEARCH_DESC desc;
+ char *f = NULL;
+ char *freeme = NULL;
+ char *p;
+
+ gpg_error_t err = classify_user_id (keyspec, &desc, 1);
+ if (err)
+ return err;
+
+ switch (desc.mode)
+ {
+ case KEYDB_SEARCH_MODE_EXACT:
+ f = xasprintf ("(pgpUserID=%s)",
+ (freeme = ldap_escape_filter (desc.u.name)));
+ break;
+
+ case KEYDB_SEARCH_MODE_SUBSTR:
+ if (! only_exact)
+ f = xasprintf ("(pgpUserID=*%s*)",
+ (freeme = ldap_escape_filter (desc.u.name)));
+ break;
+
+ case KEYDB_SEARCH_MODE_MAIL:
+ 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 ((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 ((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_SHORT_KID:
+ f = xasprintf ("(pgpKeyID=%08lX)", (ulong) desc.u.kid[1]);
+ break;
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ f = xasprintf ("(pgpCertID=%08lX%08lX)",
+ (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, 20, 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:
+ case KEYDB_SEARCH_MODE_SUBJECT:
+ case KEYDB_SEARCH_MODE_KEYGRIP:
+ case KEYDB_SEARCH_MODE_WORDS:
+ case KEYDB_SEARCH_MODE_FIRST:
+ case KEYDB_SEARCH_MODE_NEXT:
+ default:
+ break;
+ }
+
+ xfree (freeme);
+
+ if (! f)
+ {
+ log_error ("Unsupported search mode.\n");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+
+ *filter = f;
+
+ return 0;
+}
+
+
+
+/* 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;
+
+
+ object = xasprintf ("cn=pgpServerInfo,%s", basedn_search);
+
+ npth_unprotect ();
+ lerr = ldap_search_s (ldap_conn, object, LDAP_SCOPE_BASE,
+ "(objectClass=*)", attr2, 0, &si_res);
+ npth_protect ();
+ xfree (object);
+
+ 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);
+
+ 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);
+
+ 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)
+ {
+ 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);
+ }
+
+ /* 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;
+}
+
+
+
+/* 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).
+ *
+ * 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, 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
+
+ if (r_basedn)
+ *r_basedn = NULL;
+ if (r_host)
+ *r_host = NULL;
+ if (r_use_tls)
+ *r_use_tls = 0;
+ *r_serverinfo = 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;
+ }
+ }
+
+ if (opt.verbose)
+ log_info ("ldap connect to '%s:%d:%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":"");
+
+
+ /* If the uri specifies a secure connection and we don't support
+ TLS, then fail; don't silently revert to an insecure
+ connection. */
+ if (use_tls)
+ {
+#ifndef HAVE_LDAP_START_TLS_S
+ log_error ("ldap: can't connect to the server: no TLS support.");
+ err = GPG_ERR_LDAP_NOT_SUPPORTED;
+ goto out;
+#endif
+ }
+
+
+#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 = 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;
+
+ lerr = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver);
+ if (lerr != LDAP_SUCCESS)
+ {
+ log_error ("ks-ldap: unable to go to LDAP 3: %s\n",
+ ldap_err2string (lerr));
+ err = ldap_err_to_gpg_err (lerr);
+ goto out;
+ }
+ }
+ if (opt.ldaptimeout)
+ {
+ int ver = opt.ldaptimeout;
+
+ 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
+
+
+#ifdef HAVE_LDAP_START_TLS_S
+ if (use_tls == 1)
+ {
+#ifndef HAVE_W32_SYSTEM
+ int check_cert = LDAP_OPT_X_TLS_HARD; /* LDAP_OPT_X_TLS_NEVER */
+
+ lerr = ldap_set_option (ldap_conn,
+ LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert);
+ if (lerr)
+ {
+ log_error ("ldap: error setting an TLS option: %s\n",
+ ldap_err2string (lerr));
+ err = ldap_err_to_gpg_err (lerr);
+ goto out;
+ }
+#else
+ /* On Windows, the certificates are checked by default. If the
+ option to disable checking mentioned above is ever
+ implemented, the way to do that on Windows is to install a
+ callback routine using ldap_set_option (..,
+ LDAP_OPT_SERVER_CERTIFICATE, ..); */
+#endif
+
+ npth_unprotect ();
+ lerr = ldap_start_tls_s (ldap_conn,
+#ifdef HAVE_W32_SYSTEM
+ /* ServerReturnValue, result */
+ NULL, NULL,
+#endif
+ /* ServerControls, ClientControls */
+ NULL, NULL);
+ npth_protect ();
+ if (lerr)
+ {
+ log_error ("ldap: error switching to STARTTLS mode: %s\n",
+ ldap_err2string (lerr));
+ err = ldap_err_to_gpg_err (lerr);
+ goto out;
+ }
+ }
+#endif
+
+ if (use_ntds)
+ {
+#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 ("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 ();
+ /* Older Windows header dont have the const for the last two args.
+ * Thus we need to cast to avoid warnings. */
+ lerr = ldap_simple_bind_s (ldap_conn,
+ (char * const)bindname,
+ (char * const)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 (basedn_arg && *basedn_arg)
+ {
+ /* 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;
+
+ /* 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;
+ char *attr[] = { "namingContexts", NULL };
+
+ npth_unprotect ();
+ lerr = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE,
+ "(objectClass=*)", attr, 0, &res);
+ npth_protect ();
+
+ if (lerr == LDAP_SUCCESS)
+ {
+ 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. */
+ int i;
+
+ *r_serverinfo |= SERVERINFO_REALLDAP;
+
+ for (i = 0; context[i] && !basedn; i++)
+ basedn = interrogate_ldap_dn (ldap_conn, context[i],
+ r_serverinfo);
+
+ ldap_value_free (context);
+ }
+ }
+ else /* ldap_search failed. */
+ {
+ /* We don't have an answer yet, which means the server might
+ be a PGP.com keyserver. */
+ char **vals;
+ LDAPMessage *si_res = NULL;
+
+ char *attr2[] = { "pgpBaseKeySpaceDN", "version", "software", NULL };
+
+ npth_unprotect ();
+ lerr = ldap_search_s (ldap_conn, "cn=pgpServerInfo", LDAP_SCOPE_BASE,
+ "(objectClass=*)", attr2, 0, &si_res);
+ npth_protect ();
+ if (lerr == LDAP_SUCCESS)
+ {
+ /* 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 && vals[0])
+ {
+ basedn = xtrystrdup (vals[0]);
+ }
+ my_ldap_value_free (vals);
+
+ vals = ldap_get_values (ldap_conn, si_res, "software");
+ if (vals && vals[0])
+ {
+ 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 && 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,
+ but it matches how PGP does it. I figure the NAI
+ folks assumed that there would never be an LDAP
+ keyserver vendor with a different numbering
+ scheme. */
+ if (atoi (vals[0]) > 1)
+ *r_serverinfo |= SERVERINFO_PGPKEYV2;
+
+ }
+ my_ldap_value_free (vals);
+ }
+
+ ldap_msgfree (si_res);
+ }
+
+ /* 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 (res);
+ }
+
+ out:
+ if (!err && opt.debug)
+ {
+ log_debug ("ldap_conn: %p\n", ldap_conn);
+ 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",
+ (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey");
+ }
+
+ ldapserver_list_free (server);
+
+ if (err)
+ {
+ xfree (basedn);
+ if (ldap_conn)
+ ldap_unbind (ldap_conn);
+ }
+ else
+ {
+ if (r_basedn)
+ *r_basedn = basedn;
+ else
+ xfree (basedn);
+ if (r_host)
+ *r_host = host;
+ else
+ xfree (host);
+
+ *ldap_connp = ldap_conn;
+ }
+
+ return err;
+}
+
+/* Extract keys from an LDAP reply and write them out to the output
+ stream OUTPUT in a format GnuPG can import (either the OpenPGP
+ binary format or armored format). */
+static void
+extract_keys (estream_t output,
+ LDAP *ldap_conn, const char *certid, LDAPMessage *message)
+{
+ char **vals;
+
+ es_fprintf (output, "INFO %s BEGIN\n", certid);
+
+ /* 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])
+ {
+ if (strcmp (vals[0], "RSA") == 0)
+ es_fprintf (output, "1");
+ else if (strcmp (vals[0],"DSS/DH") == 0)
+ es_fprintf (output, "17");
+ }
+ my_ldap_value_free (vals);
+
+ es_fprintf (output, ":");
+
+ vals = ldap_get_values (ldap_conn, message, "pgpkeysize");
+ if (vals && vals[0])
+ {
+ int v = atoi (vals[0]);
+ if (v > 0)
+ es_fprintf (output, "%d", v);
+ }
+ my_ldap_value_free (vals);
+
+ es_fprintf (output, ":");
+
+ vals = ldap_get_values (ldap_conn, message, "pgpkeycreatetime");
+ if (vals && vals[0])
+ {
+ if (strlen (vals[0]) == 15)
+ es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0]));
+ }
+ my_ldap_value_free (vals);
+
+ es_fprintf (output, ":");
+
+ vals = ldap_get_values (ldap_conn, message, "pgpkeyexpiretime");
+ if (vals && vals[0])
+ {
+ if (strlen (vals[0]) == 15)
+ es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0]));
+ }
+ 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);
+
+ es_fprintf (output, "INFO %s END\n", certid);
+}
+
+
+/* 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_get. 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, 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, LDAP_SCOPE_SUBTREE,
+ 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: key %s not found on keyserver\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;
+}
+
+
+/* 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. KS_GET_FLAGS conveys flags from the client. */
+gpg_error_t
+ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
+ unsigned int ks_get_flags, estream_t *r_fp)
+{
+ gpg_error_t err = 0;
+ unsigned int serverinfo;
+ char *host = NULL;
+ int use_tls;
+ char *filter = NULL;
+ LDAP *ldap_conn = NULL;
+ char *basedn = NULL;
+ estream_t fp = NULL;
+ LDAPMessage *message = NULL;
+ 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[] =
+ {
+ "dummy", /* (to be be replaced.) */
+ "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled",
+ "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype",
+ "gpgfingerprint",
+ NULL
+ };
+
+ (void) ctrl;
+
+ 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);
+ }
+
+ /* Make sure we got a state. */
+ 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;
+ }
+
+
+ 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;
+ }
+ 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->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_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;
+ }
+ }
+
+ 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. */
+ {
+ /* Make sure we are talking to an OpenPGP LDAP server. */
+ err = my_ldap_connect (uri, &ldap_conn,
+ &basedn, &host, &use_tls, &serverinfo);
+ if (err || !basedn)
+ {
+ if (!err)
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+
+ /* 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;
+
+ if (opt.debug)
+ log_debug ("ks-ldap: using filter: %s\n", filter);
+
+ /* Replace "dummy". */
+ attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey";
+
+ err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, 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_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;
+ }
+
+ 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 && 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->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;
+ }
+
+ if (message)
+ ldap_msgfree (message);
+
+ if (err)
+ es_fclose (fp);
+ else
+ {
+ if (fp)
+ es_fseek (fp, 0, SEEK_SET);
+ *r_fp = fp;
+ }
+
+ free_strlist (seen);
+ xfree (basedn);
+ xfree (host);
+
+ if (ldap_conn)
+ ldap_unbind (ldap_conn);
+
+ xfree (filter);
+
+ 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
+ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
+ estream_t *r_fp)
+{
+ 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 (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);
+ }
+
+ /* Make sure we are talking to an OpenPGP LDAP server. */
+ err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
+ if (err || !basedn)
+ {
+ if (!err)
+ err = GPG_ERR_GENERAL;
+ goto out;
+ }
+
+ /* 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)
+ {
+ log_error ("Bad search pattern: '%s'\n", pattern);
+ goto out;
+ }
+
+ /* Even if we have no results, we want to return a stream. */
+ fp = es_fopenmem(0, "rw");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ goto out;
+ }
+
+ {
+ char **vals;
+ LDAPMessage *res, *each;
+ int count = 0;
+ strlist_t dupelist = NULL;
+
+ /* The maximum size of the search, including the optional stuff
+ and the trailing \0 */
+ char *attrs[] =
+ {
+ "pgpcertid", "pgpuserid", "pgprevoked", "pgpdisabled",
+ "pgpkeycreatetime", "pgpkeyexpiretime", "modifytimestamp",
+ "pgpkeysize", "pgpkeytype", "gpgfingerprint",
+ NULL
+ };
+
+ 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;
+
+ if (ldap_err != LDAP_SUCCESS && ldap_err != LDAP_SIZELIMIT_EXCEEDED)
+ {
+ err = ldap_err_to_gpg_err (ldap_err);
+
+ log_error ("SEARCH %s FAILED %d\n", pattern, err);
+ 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 (npth_unprotect (),
+ each = ldap_first_entry (ldap_conn, res),
+ npth_protect ();
+ 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]))
+ {
+ add_to_strlist (&dupelist, certid[0]);
+ count++;
+ }
+ my_ldap_value_free (certid);
+ }
+
+ if (ldap_err == LDAP_SIZELIMIT_EXCEEDED)
+ {
+ if (count == 1)
+ log_error ("ks-ldap: search results exceeded server limit."
+ " First 1 result shown.\n");
+ else
+ log_error ("ks-ldap: search results exceeded server limit."
+ " First %d results shown.\n", count);
+ }
+
+ free_strlist (dupelist);
+ dupelist = NULL;
+
+ if (count < 1)
+ es_fputs ("info:1:0\n", fp);
+ else
+ {
+ es_fprintf (fp, "info:1:%d\n", count);
+
+ for (each = ldap_first_entry (ldap_conn, res);
+ each;
+ each = ldap_next_entry (ldap_conn, each))
+ {
+ char **certid;
+ LDAPMessage *uids;
+
+ certid = ldap_get_values (ldap_conn, each, "pgpcertid");
+ 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]);
+
+ 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 && vals[0])
+ {
+ /* The LDAP server doesn't exactly handle this
+ well. */
+ if (strcasecmp (vals[0], "RSA") == 0)
+ es_fputs ("1", fp);
+ else if (strcasecmp (vals[0], "DSS/DH") == 0)
+ es_fputs ("17", fp);
+ }
+ my_ldap_value_free (vals);
+
+ es_fputc (':', fp);
+
+ vals = ldap_get_values (ldap_conn, each, "pgpkeysize");
+ 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]));
+ }
+ my_ldap_value_free (vals);
+
+ es_fputc (':', fp);
+
+ /* YYYYMMDDHHmmssZ */
+
+ vals = ldap_get_values (ldap_conn, each, "pgpkeycreatetime");
+ if(vals && vals[0] && strlen (vals[0]) == 15)
+ {
+ es_fprintf (fp, "%u",
+ (unsigned int) ldap2epochtime(vals[0]));
+ }
+ my_ldap_value_free (vals);
+
+ es_fputc (':', fp);
+
+ vals = ldap_get_values (ldap_conn, each, "pgpkeyexpiretime");
+ if (vals && vals[0] && strlen (vals[0]) == 15)
+ {
+ es_fprintf (fp, "%u",
+ (unsigned int) ldap2epochtime (vals[0]));
+ }
+ my_ldap_value_free (vals);
+
+ es_fputc (':', fp);
+
+ vals = ldap_get_values (ldap_conn, each, "pgprevoked");
+ if (vals && vals[0])
+ {
+ if (atoi (vals[0]) == 1)
+ es_fprintf (fp, "r");
+ }
+ my_ldap_value_free (vals);
+
+ vals = ldap_get_values (ldap_conn, each, "pgpdisabled");
+ if (vals && vals[0])
+ {
+ if (atoi (vals[0]) ==1)
+ es_fprintf (fp, "d");
+ }
+ 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 && vals[0] strlen (vals[0]) == 15)
+ {
+ es_fprintf (fp, "%u",
+ (unsigned int) ldap2epochtime (vals[0]));
+ }
+ my_ldap_value_free (vals);
+#endif
+
+ es_fprintf (fp, "\n");
+
+ /* Now print all the uids that have this certid */
+ for (uids = ldap_first_entry (ldap_conn, res);
+ uids;
+ uids = ldap_next_entry (ldap_conn, uids))
+ {
+ vals = ldap_get_values (ldap_conn, uids, "pgpcertid");
+ if (!vals || !vals[0])
+ {
+ my_ldap_value_free (vals);
+ continue;
+ }
+
+ if (!ascii_strcasecmp (certid[0], vals[0]))
+ {
+ char **uidvals;
+
+ es_fprintf (fp, "uid:");
+
+ uidvals = ldap_get_values (ldap_conn,
+ uids, "pgpuserid");
+ if (uidvals)
+ {
+ /* Need to percent escape any colons */
+ char *quoted = try_percent_escape (uidvals[0],
+ NULL);
+ if (quoted)
+ es_fputs (quoted, fp);
+ xfree (quoted);
+ }
+ my_ldap_value_free (uidvals);
+
+ es_fprintf (fp, "\n");
+ }
+
+ ldap_value_free(vals);
+ }
+ }
+
+ my_ldap_value_free (certid);
+ }
+ }
+
+ ldap_msgfree (res);
+ free_strlist (dupelist);
+ }
+
+ if (opt.debug)
+ log_debug ("SEARCH %s END\n", pattern);
+
+ out:
+ if (err)
+ {
+ es_fclose (fp);
+ }
+ else
+ {
+ /* Return the read stream. */
+ if (fp)
+ es_fseek (fp, 0, SEEK_SET);
+
+ *r_fp = fp;
+ }
+
+ xfree (basedn);
+
+ if (ldap_conn)
+ ldap_unbind (ldap_conn);
+
+ xfree (filter);
+
+ return err;
+}
+
+
+
+/* A modlist describes a set of changes to an LDAP entry. (An entry
+ consists of 1 or more attributes. Attributes are <name, value>
+ pairs. Note: an attribute may be multi-valued in which case
+ multiple values are associated with a single name.)
+
+ A modlist is a NULL terminated array of struct LDAPMod's.
+
+ Thus, if we have:
+
+ LDAPMod **modlist;
+
+ Then:
+
+ modlist[i]
+
+ Is the ith modification.
+
+ Each LDAPMod describes a change to a single attribute. Further,
+ there is one modification for each attribute that we want to
+ change. The attribute's new value is stored in LDAPMod.mod_values.
+ If the attribute is multi-valued, we still only use a single
+ LDAPMod structure: mod_values is a NULL-terminated array of
+ strings. To delete an attribute from an entry, we set mod_values
+ to NULL.
+
+ Thus, if:
+
+ modlist[i]->mod_values == NULL
+
+ then we remove the attribute.
+
+ (Using LDAP_MOD_DELETE doesn't work here as we don't know if the
+ attribute in question exists or not.)
+
+ Note: this function does NOT copy or free ATTR. It does copy
+ VALUE. */
+static void
+modlist_add (LDAPMod ***modlistp, char *attr, const char *value)
+{
+ LDAPMod **modlist = *modlistp;
+
+ LDAPMod **m;
+ int nummods = 0;
+
+ /* Search modlist for the attribute we're playing with. If modlist
+ is NULL, then the list is empty. Recall: modlist is a NULL
+ terminated array. */
+ for (m = modlist; m && *m; m++, nummods ++)
+ {
+ /* The attribute is already on the list. */
+ char **ptr;
+ int numvalues = 0;
+
+ if (strcasecmp ((*m)->mod_type, attr) != 0)
+ continue;
+
+ /* We have this attribute already, so when the REPLACE happens,
+ the server attributes will be replaced anyway. */
+ if (! value)
+ return;
+
+ /* Attributes can be multi-valued. See if the value is already
+ present. mod_values is a NULL terminated array of pointers.
+ Note: mod_values can be NULL. */
+ for (ptr = (*m)->mod_values; ptr && *ptr; ptr++)
+ {
+ if (strcmp (*ptr, value) == 0)
+ /* Duplicate value, we're done. */
+ return;
+ numvalues ++;
+ }
+
+ /* Append the value. */
+ ptr = xrealloc ((*m)->mod_values, sizeof (char *) * (numvalues + 2));
+
+ (*m)->mod_values = ptr;
+ ptr[numvalues] = xstrdup (value);
+
+ ptr[numvalues + 1] = NULL;
+
+ return;
+ }
+
+ /* We didn't find the attr, so make one and add it to the end */
+
+ /* Like attribute values, the list of attributes is NULL terminated
+ array of pointers. */
+ modlist = xrealloc (modlist, sizeof (LDAPMod *) * (nummods + 2));
+
+ *modlistp = modlist;
+ modlist[nummods] = xmalloc (sizeof (LDAPMod));
+
+ modlist[nummods]->mod_op = LDAP_MOD_REPLACE;
+ modlist[nummods]->mod_type = attr;
+ if (value)
+ {
+ modlist[nummods]->mod_values = xmalloc (sizeof(char *) * 2);
+
+ modlist[nummods]->mod_values[0] = xstrdup (value);
+ modlist[nummods]->mod_values[1] = NULL;
+ }
+ else
+ modlist[nummods]->mod_values = NULL;
+
+ modlist[nummods + 1] = NULL;
+
+ return;
+}
+
+/* Look up the value of an attribute in the specified modlist. If the
+ attribute is not on the mod list, returns NULL. The result is a
+ NULL-terminated array of strings. Don't change it. */
+static char **
+modlist_lookup (LDAPMod **modlist, const char *attr)
+{
+ LDAPMod **m;
+ for (m = modlist; m && *m; m++)
+ {
+ if (strcasecmp ((*m)->mod_type, attr) != 0)
+ continue;
+
+ return (*m)->mod_values;
+ }
+
+ return NULL;
+}
+
+/* Dump a modlist to a file. This is useful for debugging. */
+static estream_t modlist_dump (LDAPMod **modlist, estream_t output)
+ GPGRT_ATTR_USED;
+
+static estream_t
+modlist_dump (LDAPMod **modlist, estream_t output)
+{
+ LDAPMod **m;
+
+ int opened = 0;
+
+ if (! output)
+ {
+ output = es_fopenmem (0, "rw");
+ if (!output)
+ return NULL;
+ opened = 1;
+ }
+
+ for (m = modlist; m && *m; m++)
+ {
+ es_fprintf (output, " %s:", (*m)->mod_type);
+
+ if (! (*m)->mod_values)
+ es_fprintf(output, " delete.\n");
+ else
+ {
+ char **ptr;
+ int i;
+
+ int multi = 0;
+ if ((*m)->mod_values[0] && (*m)->mod_values[1])
+ /* Have at least 2. */
+ multi = 1;
+
+ if (multi)
+ es_fprintf (output, "\n");
+
+ for ((ptr = (*m)->mod_values), (i = 1); ptr && *ptr; ptr++, i ++)
+ {
+ /* Assuming terminals are about 80 characters wide,
+ 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;
+ size_t value_len = strlen (*ptr);
+ int elide = value_len > max_len;
+
+ if (multi)
+ es_fprintf (output, " %d. ", i);
+ es_fprintf (output, "`%.*s", max_len, *ptr);
+ if (elide)
+ es_fprintf (output, "...' (%zd bytes elided)",
+ value_len - max_len);
+ else
+ es_fprintf (output, "'");
+ es_fprintf (output, "\n");
+ }
+ }
+ }
+
+ if (opened)
+ es_fseek (output, 0, SEEK_SET);
+
+ return output;
+}
+
+/* Free all of the memory allocated by the mod list. This assumes
+ that the attribute names don't have to be freed, but the attributes
+ values do. (Which is what modlist_add does.) */
+static void
+modlist_free (LDAPMod **modlist)
+{
+ LDAPMod **ml;
+
+ if (! modlist)
+ return;
+
+ /* Unwind and free the whole modlist structure */
+
+ /* The modlist is a NULL terminated array of pointers. */
+ for (ml = modlist; *ml; ml++)
+ {
+ LDAPMod *mod = *ml;
+ char **ptr;
+
+ /* The list of values is a NULL termianted array of pointers.
+ If the list is NULL, there are no values. */
+
+ if (mod->mod_values)
+ {
+ for (ptr = mod->mod_values; *ptr; ptr++)
+ xfree (*ptr);
+
+ xfree (mod->mod_values);
+ }
+
+ xfree (mod);
+ }
+ xfree (modlist);
+}
+
+/* Append two onto the end of one. Two is not freed, but its pointers
+ are now part of one. Make sure you don't free them both!
+
+ As long as you don't add anything to ONE, TWO is still valid.
+ After that all bets are off. */
+static void
+modlists_join (LDAPMod ***one, LDAPMod **two)
+{
+ int i, one_count = 0, two_count = 0;
+ LDAPMod **grow;
+
+ if (!*two)
+ /* two is empty. Nothing to do. */
+ return;
+
+ if (!*one)
+ /* one is empty. Just set it equal to *two. */
+ {
+ *one = two;
+ return;
+ }
+
+ for (grow = *one; *grow; grow++)
+ one_count ++;
+
+ for (grow = two; *grow; grow++)
+ two_count ++;
+
+ grow = xrealloc (*one, sizeof(LDAPMod *) * (one_count + two_count + 1));
+
+ for (i = 0; i < two_count; i++)
+ grow[one_count + i] = two[i];
+
+ grow[one_count + i] = NULL;
+
+ *one = grow;
+}
+
+/* Given a string, unescape C escapes. In particular, \xXX. This
+ modifies the string in place. */
+static void
+uncescape (char *str)
+{
+ size_t r = 0;
+ size_t w = 0;
+
+ char *first = strchr (str, '\\');
+ if (! first)
+ /* No backslashes => no escaping. We're done. */
+ return;
+
+ /* Start at the first '\\'. */
+ r = w = (uintptr_t) first - (uintptr_t) str;
+
+ while (str[r])
+ {
+ /* XXX: What to do about bad escapes?
+ XXX: hextobyte already checks the string thus the hexdigitp
+ could be removed. */
+ if (str[r] == '\\' && str[r + 1] == 'x'
+ && str[r+2] && str[r+3]
+ && hexdigitp (str + r + 2)
+ && hexdigitp (str + r + 3))
+ {
+ int x = hextobyte (&str[r + 2]);
+ log_assert (0 <= x && x <= 0xff);
+
+ str[w] = x;
+
+ /* We consumed 4 characters and wrote 1. */
+ r += 4;
+ w ++;
+ }
+ else
+ str[w ++] = str[r ++];
+ }
+
+ str[w] = '\0';
+}
+
+/* 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. 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, 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 */
+ trim_trailing_spaces (line);
+
+ fields = strsplit (line, ':', '\0', &field_count);
+ if (field_count == 1)
+ /* We only have a single field. There is definitely nothing to
+ do. */
+ goto out;
+
+ if (field_count < 7)
+ goto out;
+
+ 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)
+ goto out; /* Not a relevant line. */
+
+ keyid = fields[4];
+
+ if (is_uid && strlen (keyid) == 0)
+ ; /* 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. */
+ else
+ {
+ log_error ("malformed record!\n");
+ goto out;
+ }
+
+ if (is_pub)
+ {
+ int disabled = 0;
+ int revoked = 0;
+ char *flags;
+ for (flags = fields[1]; *flags; flags ++)
+ switch (*flags)
+ {
+ case 'r':
+ case 'R':
+ revoked = 1;
+ break;
+
+ case 'd':
+ case 'D':
+ disabled = 1;
+ break;
+ }
+
+ /* Note: we always create the pgpDisabled and pgpRevoked
+ attributes, regardless of whether the key is disabled/revoked
+ or not. This is because a very common search is like
+ "(&(pgpUserID=*isabella*)(pgpDisabled=0))" */
+
+ if (is_pub)
+ {
+ modlist_add (modlist,"pgpDisabled", disabled ? "1" : "0");
+ modlist_add (modlist,"pgpRevoked", revoked ? "1" : "0");
+ }
+ }
+
+ if (is_pub || is_sub)
+ {
+ char padded[6];
+ int val;
+
+ 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)
+ {
+ char *algo = fields[3];
+ int val = atoi (algo);
+ switch (val)
+ {
+ case 1:
+ algo = "RSA";
+ break;
+
+ case 17:
+ algo = "DSS/DH";
+ break;
+
+ default:
+ algo = NULL;
+ break;
+ }
+
+ if (algo)
+ modlist_add (modlist, "pgpKeyType", algo);
+ }
+
+ if (is_pub || is_sub || is_sig)
+ {
+ if (is_pub)
+ {
+ modlist_add (modlist, "pgpCertID", keyid); /* Long keyid(!) */
+ modlist_add (modlist, "pgpKeyID", &keyid[8]); /* Short keyid */
+ }
+
+ if (is_sub)
+ modlist_add (modlist, "pgpSubKeyID", keyid); /* Long keyid(!) */
+ }
+
+ if (is_pub)
+ {
+ char *create_time = fields[5];
+
+ if (strlen (create_time) == 0)
+ create_time = NULL;
+ else
+ {
+ char *create_time_orig = create_time;
+ struct tm tm;
+ time_t t;
+ char *end;
+
+ memset (&tm, 0, sizeof (tm));
+
+ /* parse_timestamp handles both seconds fromt he epoch and
+ ISO 8601 format. We also need to handle YYYY-MM-DD
+ format (as generated by gpg1 --with-colons --list-key).
+ Check that first and then if it fails, then try
+ parse_timestamp. */
+
+ if (!isodate_human_to_tm (create_time, &tm))
+ create_time = tm2ldaptime (&tm);
+ else if ((t = parse_timestamp (create_time, &end)) != (time_t) -1
+ && *end == '\0')
+ {
+
+ if (!gnupg_gmtime (&t, &tm))
+ create_time = NULL;
+ else
+ create_time = tm2ldaptime (&tm);
+ }
+ else
+ create_time = NULL;
+
+ if (! create_time)
+ /* Failed to parse string. */
+ log_error ("Failed to parse creation time ('%s')",
+ create_time_orig);
+ }
+
+ if (create_time)
+ {
+ modlist_add (modlist, "pgpKeyCreateTime", create_time);
+ xfree (create_time);
+ }
+ }
+
+ if (is_pub)
+ {
+ char *expire_time = fields[6];
+
+ if (strlen (expire_time) == 0)
+ expire_time = NULL;
+ else
+ {
+ char *expire_time_orig = expire_time;
+ struct tm tm;
+ time_t t;
+ char *end;
+
+ memset (&tm, 0, sizeof (tm));
+
+ /* parse_timestamp handles both seconds fromt he epoch and
+ ISO 8601 format. We also need to handle YYYY-MM-DD
+ format (as generated by gpg1 --with-colons --list-key).
+ Check that first and then if it fails, then try
+ parse_timestamp. */
+
+ if (!isodate_human_to_tm (expire_time, &tm))
+ expire_time = tm2ldaptime (&tm);
+ else if ((t = parse_timestamp (expire_time, &end)) != (time_t) -1
+ && *end == '\0')
+ {
+ if (!gnupg_gmtime (&t, &tm))
+ expire_time = NULL;
+ else
+ expire_time = tm2ldaptime (&tm);
+ }
+ else
+ expire_time = NULL;
+
+ if (! expire_time)
+ /* Failed to parse string. */
+ log_error ("Failed to parse creation time ('%s')",
+ expire_time_orig);
+ }
+
+ if (expire_time)
+ {
+ modlist_add (modlist, "pgpKeyExpireTime", expire_time);
+ xfree (expire_time);
+ }
+ }
+
+ if (is_uid && field_count >= 10)
+ {
+ char *uid = fields[9];
+ char *mbox;
+
+ uncescape (uid);
+ modlist_add (modlist, "pgpUserID", uid);
+ if (schemav2 && (mbox = mailbox_from_userid (uid)))
+ {
+ modlist_add (modlist, "gpgMailbox", mbox);
+ xfree (mbox);
+ }
+ }
+
+ out:
+ xfree (fields);
+}
+
+/* Send the key in {KEY,KEYLEN} with the metadata {INFO,INFOLEN} to
+ the keyserver identified by URI. See server.c:cmd_ks_put for the
+ format of the data and metadata. */
+gpg_error_t
+ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
+ void *data, size_t datalen,
+ void *info, size_t infolen)
+{
+ gpg_error_t err = 0;
+ int ldap_err;
+ unsigned int serverinfo;
+ LDAP *ldap_conn = NULL;
+ char *basedn = NULL;
+ 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;
+
+ /* Enable this code to dump the modlist to /tmp/modlist.txt. */
+#if 0
+# warning Disable debug code before checking in.
+ const int dump_modlist = 1;
+#else
+ const int dump_modlist = 0;
+#endif
+ estream_t dump = NULL;
+
+ /* Elide a warning. */
+ (void) ctrl;
+
+ 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);
+ }
+
+ err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
+ if (err || !basedn)
+ {
+ if (!err)
+ err = GPG_ERR_GENERAL;
+ goto out;
+ }
+
+ if (!(serverinfo & SERVERINFO_REALLDAP))
+ {
+ /* 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 = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey";
+ mod.mod_values = key;
+ attrs[0] = &mod;
+ attrs[1] = NULL;
+
+ 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);
+
+ if (ldap_err != LDAP_SUCCESS)
+ {
+ err = ldap_err_to_gpg_err (err);
+ goto out;
+ }
+
+ goto out;
+ }
+
+ 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",
+ gpg_strerror (gpg_error_from_syserror ()));
+
+ if (dump)
+ {
+ es_fprintf(dump, "data (%zd bytes)\n", datalen);
+ es_fprintf(dump, "info (%zd bytes): '\n", infolen);
+ es_fwrite(info, infolen, 1, dump);
+ es_fprintf(dump, "'\n");
+ }
+ }
+
+ /* Start by nulling out all attributes. We try and do a modify
+ operation first, so this ensures that we don't leave old
+ attributes lying around. */
+ modlist_add (&modlist, "pgpDisabled", NULL);
+ modlist_add (&modlist, "pgpKeyID", NULL);
+ modlist_add (&modlist, "pgpKeyType", NULL);
+ modlist_add (&modlist, "pgpUserID", NULL);
+ modlist_add (&modlist, "pgpKeyCreateTime", 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;
+
+ char *newline = memchr (info, '\n', infolen);
+ if (! newline)
+ /* The last line is not \n terminated! Make a copy so we can
+ add a NUL terminator. */
+ {
+ temp = xmalloc (infolen + 1);
+ memcpy (temp, info, infolen);
+ info = temp;
+ newline = (char *) info + infolen;
+ }
+
+ *newline = '\0';
+
+ 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)
+ log_assert ((char *) info + infolen - 1 == infoend);
+ else
+ {
+ log_assert (infolen == -1);
+ xfree (temp);
+ }
+ }
+
+ modlist_add (&addlist, "objectClass", "pgpKeyInfo");
+
+ err = armor_data (&data_armored, data, datalen);
+ if (err)
+ goto out;
+
+ modlist_add (&addlist,
+ (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey",
+ data_armored);
+
+ /* Now append addlist onto modlist. */
+ modlists_join (&modlist, addlist);
+
+ if (dump)
+ {
+ estream_t input = modlist_dump (modlist, NULL);
+ if (input)
+ {
+ copy_stream (input, dump);
+ es_fclose (input);
+ }
+ }
+
+ /* Going on the assumption that modify operations are more frequent
+ than adds, we try a modify first. If it's not there, we just
+ turn around and send an add command for the same key. Otherwise,
+ the modify brings the server copy into compliance with our copy.
+ Note that unlike the LDAP keyserver (and really, any other
+ keyserver) this does NOT merge signatures, but replaces the whole
+ key. This should make some people very happy. */
+ {
+ char **attrval;
+ char *dn;
+
+ if ((serverinfo & SERVERINFO_NTDS))
+ {
+ /* 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);
+
+ 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 ("ks-ldap: error adding key to keyserver: %s\n",
+ ldap_err2string (err));
+ err = ldap_err_to_gpg_err (err);
+ }
+ }
+
+ out:
+ if (dump)
+ es_fclose (dump);
+
+ if (ldap_conn)
+ ldap_unbind (ldap_conn);
+
+ xfree (basedn);
+
+ modlist_free (modlist);
+ xfree (addlist);
+
+ xfree (data_armored);
+
+ return err;
+}