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