diff options
Diffstat (limited to 'dirmngr/ks-engine-ldap.c')
-rw-r--r-- | dirmngr/ks-engine-ldap.c | 864 |
1 files changed, 785 insertions, 79 deletions
diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index 22f974c..b416ac0 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -1,7 +1,7 @@ /* ks-engine-ldap.c - talk to a LDAP keyserver * Copyright (C) 2001, 2002, 2004, 2005, 2006 * 2007 Free Software Foundation, Inc. - * Copyright (C) 2015, 2020 g10 Code GmbH + * Copyright (C) 2015, 2020, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -29,12 +29,20 @@ #endif #include <stdlib.h> #include <npth.h> +#ifdef HAVE_W32_SYSTEM +# ifndef WINVER +# define WINVER 0x0500 /* Same as in common/sysutils.c */ +# endif +# 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" @@ -46,6 +54,7 @@ #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. */ /* The page size requested from the server. */ @@ -64,6 +73,7 @@ struct ks_engine_ldap_local_s LDAPMessage *message; LDAPMessage *msg_iter; /* Iterator for message. */ unsigned int serverinfo; + int scope; char *basedn; char *keyspec; char *filter; @@ -73,6 +83,9 @@ struct ks_engine_ldap_local_s 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); @@ -150,6 +163,114 @@ my_ldap_value_free (char **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; +} + + /* Print a help output for the schemata supported by this module. */ gpg_error_t @@ -196,7 +317,12 @@ ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri) static struct ks_engine_ldap_local_s * ks_ldap_new_state (void) { - return xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s)); + 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; } @@ -221,6 +347,7 @@ ks_ldap_clear_state (struct ks_engine_ldap_local_s *state) } state->serverinfo = 0; xfree (state->basedn); + state->scope = LDAP_SCOPE_SUBTREE; state->basedn = NULL; xfree (state->keyspec); state->keyspec = NULL; @@ -244,6 +371,45 @@ ks_ldap_free_state (struct ks_engine_ldap_local_s *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; +} + + /* 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 @@ -443,7 +609,9 @@ interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search, * * 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). + * 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 @@ -456,7 +624,7 @@ interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search, * 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, +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) { @@ -525,15 +693,15 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, } if (opt.verbose) - log_info ("ldap connect to '%s:%d:%s:%s:%s:%s%s%s'\n", + 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":""); - + 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 @@ -541,7 +709,7 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, if (use_tls) { #ifndef HAVE_LDAP_START_TLS_S - log_error ("ldap: can't 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 @@ -613,6 +781,8 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, { 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) { @@ -710,7 +880,21 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, /* By default we don't bind as there is usually no need to. */ } - if (basedn_arg && *basedn_arg) + 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) { /* User specified base DN. In this case we know the server is a * real LDAP server. */ @@ -830,11 +1014,15 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, 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 ("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", - (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey"); + if (!(*r_serverinfo & SERVERINFO_GENERIC)) + log_debug ("pgpkeyattr: %s\n", + (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey"); } ldapserver_list_free (server); @@ -945,10 +1133,32 @@ extract_keys (estream_t output, } 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 err; +} + + /* 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. */ @@ -1026,11 +1236,132 @@ return_one_keyblock (LDAP *ldap_conn, LDAPMessage *msg, unsigned int serverinfo, } -/* Helper for ks_ldap_get. Note that KEYSPEC is only used for - * diagnostics. */ +/* 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, char *filter, + LDAP *ldap_conn, char *basedn, int scope, char *filter, char **attrs, LDAPMessage **r_message) { gpg_error_t err = 0; @@ -1063,7 +1394,7 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, } npth_unprotect (); - l_err = ldap_search_ext_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, + l_err = ldap_search_ext_s (ldap_conn, basedn, scope, filter, attrs, 0, srvctrls[0]? srvctrls : NULL, NULL, NULL, 0, r_message); @@ -1128,7 +1459,7 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, 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); + log_info ("ks-ldap: '%s' not found on LDAP server\n", keyspec); if (count == -1) err = ldap_to_gpg_err (ldap_conn); @@ -1148,20 +1479,149 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, } +/* Fetch all entries from the RootDSE and return them as a name value + * object. */ +static nvc_t +fetch_rootdse (ctrl_t ctrl, parsed_uri_t uri) +{ + gpg_error_t err; + estream_t infp = NULL; + uri_item_t puri; /* The broken down URI (only one item used). */ + nvc_t nvc = NULL; + + /* FIXME: We need the unparsed URI here - use uri_item_t instead + * of fix the parser to fill in original */ + err = ks_action_parse_uri (uri && uri->original? uri->original : "ldap://", + &puri); + if (err) + return NULL; + + /* Reset authentication for a serverless. */ + puri->parsed_uri->ad_current = 0; + puri->parsed_uri->auth = NULL; + + if (!strcmp (puri->parsed_uri->scheme, "ldap") + || !strcmp (puri->parsed_uri->scheme, "ldaps") + || !strcmp (puri->parsed_uri->scheme, "ldapi") + || puri->parsed_uri->opaque) + { + err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_ROOTDSE, + "^&base&(objectclass=*)", NULL, NULL, &infp); + if (err) + log_error ("ldap: reading the rootDES failed: %s\n", + gpg_strerror (err)); + else if ((err = nvc_parse (&nvc, NULL, infp))) + log_error ("parsing the rootDES failed: %s\n", gpg_strerror (err)); + } + + es_fclose (infp); + release_uri_item_list (puri); + if (err) + { + nvc_release (nvc); + nvc = NULL; + } + return nvc; +} + + +/* Return the DN for the given RID. This is used with the Active + * Directory. */ +static char * +map_rid_to_dn (ctrl_t ctrl, const char *rid) +{ + gpg_error_t err; + char *result = NULL; + estream_t infp = NULL; + uri_item_t puri; /* The broken down URI. */ + nvc_t nvc = NULL; + char *filter = NULL; + const char *s; + char *attr[2] = {"dn", NULL}; + + err = ks_action_parse_uri ("ldap:///", &puri); + if (err) + return NULL; + + filter = strconcat ("(objectSid=S-1-5-21-$sid_domain-", rid, ")", NULL); + if (!filter) + goto leave; + + err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_SUBST, + filter, attr, NULL, &infp); + if (err) + { + log_error ("ldap: AD query '%s' failed: %s\n", filter,gpg_strerror (err)); + goto leave; + } + if ((err = nvc_parse (&nvc, NULL, infp))) + { + log_error ("ldap: parsing the result failed: %s\n",gpg_strerror (err)); + goto leave; + } + if (!(s = nvc_get_string (nvc, "Dn:"))) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + log_error ("ldap: mapping rid '%s'failed: %s\n", rid, gpg_strerror (err)); + goto leave; + } + result = xtrystrdup (s); + if (!result) + { + err = gpg_error_from_syserror (); + log_error ("ldap: strdup failed: %s\n", gpg_strerror (err)); + goto leave; + } + + leave: + es_fclose (infp); + release_uri_item_list (puri); + xfree (filter); + nvc_release (nvc); + return result; +} + + +/* 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) + { + 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"); + } + } + s = nvc_get_string (ctrl->rootdse, "defaultNamingContext:"); + return s? xtrystrdup (s): NULL; +} + + + + /* 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) + unsigned int ks_get_flags, gnupg_isotime_t newer, estream_t *r_fp) { - gpg_error_t err = 0; + gpg_error_t err; unsigned int serverinfo; char *host = NULL; int use_tls; char *filter = NULL; LDAP *ldap_conn = NULL; char *basedn = NULL; + int scope = LDAP_SCOPE_SUBTREE; estream_t fp = NULL; LDAPMessage *message = NULL; LDAPMessage *msg; @@ -1177,48 +1637,18 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, { "dummy", /* (to be be replaced.) */ "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled", - "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype", + "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; - } + return no_ldap_due_to_tor (ctrl); + err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode); + if (err) + return err; if (next_mode) { @@ -1236,6 +1666,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, 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); @@ -1284,7 +1715,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, else /* Not in --next mode. */ { /* Make sure we are talking to an OpenPGP LDAP server. */ - err = my_ldap_connect (uri, &ldap_conn, + err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, &host, &use_tls, &serverinfo); if (err || !basedn) { @@ -1305,14 +1736,36 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, if (err) goto leave; + 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 (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); + err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, scope, + filter, attrs, &message); if (err) goto leave; @@ -1363,6 +1816,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, 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; @@ -1418,14 +1872,10 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, (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); /* Make sure we are talking to an OpenPGP LDAP server. */ - err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo); + err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo); if (err || !basedn) { if (!err) @@ -1461,7 +1911,7 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, char *attrs[] = { "pgpcertid", "pgpuserid", "pgprevoked", "pgpdisabled", - "pgpkeycreatetime", "pgpkeyexpiretime", "modifytimestamp", + "pgpkeycreatetime", "pgpkeyexpiretime", "modifyTimestamp", "pgpkeysize", "pgpkeytype", "gpgfingerprint", NULL }; @@ -1615,19 +2065,17 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, } 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) + vals = ldap_get_values (ldap_conn, each, "modifyTimestamp"); + if(vals && vals[0]) { - es_fprintf (fp, "%u", - (unsigned int) ldap2epochtime (vals[0])); + gnupg_isotime_t atime; + if (rfc4517toisotime (atime, vals[0])) + *atime = 0; + es_fprintf (fp, "%s", atime); } my_ldap_value_free (vals); -#endif es_fprintf (fp, "\n"); @@ -2310,13 +2758,9 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, (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); - err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo); + err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo); if (err || !basedn) { if (!err) @@ -2542,3 +2986,265 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, return err; } + + + +/* 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; +} |