From 7a01e806eac4cd7a65eaf3e17dcd2f117ec2d327 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 5 Oct 2022 15:15:14 +0200 Subject: [PATCH] dirmngr: Support paged LDAP mode for KS_GET * dirmngr/ks-engine-ldap.c (PAGE_SIZE): New. (struct ks_engine_ldap_local_s): Add several new fields. (ks_ldap_clear_state): Release them. (search_and_parse): Factored out from ks_ldap_get and extended to support the paged mode. (ks_ldap_get): Implement the pages mode for --first and --next. * dirmngr/server.c (cmd_ks_get): Provide a dummy passphrase in --first mode. * dirmngr/Makefile.am (dirmngr_LDADD): Add LBER_LIBS. -- The paged mode allows to retrieve more items than the servers usually limit (e.g. 1000 for an LDS). This patch also allows to use --first without a patter to retrieve all keyblocks (except for disabled and revoked keys). GnuPG-bug-id: 6224 --- dirmngr/Makefile.am | 2 +- dirmngr/ks-engine-ldap.c | 238 +++++++++++++++++++++++++++++++++------ dirmngr/server.c | 11 ++ 3 files changed, 214 insertions(+), 37 deletions(-) diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index f346e693e..35fb1cf33 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -91,7 +91,7 @@ dirmngr_LDADD = $(libcommonpth) \ $(NETLIBS) if USE_LDAP -dirmngr_LDADD += $(ldaplibs) +dirmngr_LDADD += $(ldaplibs) $(LBER_LIBS) endif dirmngr_LDFLAGS = $(extra_bin_ldflags) diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index e225b67b7..0912c6d02 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -45,6 +45,10 @@ #define SERVERINFO_NTDS 8 /* Server is an Active Directory. */ +/* The page size requested from the server. */ +#define PAGE_SIZE 100 + + #ifndef HAVE_TIMEGM time_t timegm(struct tm *tm); #endif @@ -57,6 +61,13 @@ struct ks_engine_ldap_local_s LDAPMessage *message; LDAPMessage *msg_iter; /* Iterator for message. */ unsigned int serverinfo; + char *basedn; + char *keyspec; + char *filter; + struct berval *pagecookie; + unsigned int pageno; /* Current page number (starting at 1). */ + unsigned int total; /* Total number of attributes read. */ + int more_pages; /* More pages announced by server. */ }; @@ -199,7 +210,21 @@ ks_ldap_clear_state (struct ks_engine_ldap_local_s *state) ldap_msgfree (state->message); state->message = NULL; } + if (state->pagecookie) + { + ber_bvfree (state->pagecookie); + state->pagecookie = NULL; + } state->serverinfo = 0; + xfree (state->basedn); + state->basedn = NULL; + xfree (state->keyspec); + state->keyspec = NULL; + xfree (state->filter); + state->filter = NULL; + state->pageno = 0; + state->total = 0; + state->more_pages = 0; return state; } @@ -1003,6 +1028,127 @@ return_one_keyblock (LDAP *ldap_conn, LDAPMessage *msg, unsigned int serverinfo, } +/* Helper for ks_ldap_get. Note that KEYSPEC is only used for + * diagnostics. */ +static gpg_error_t +search_and_parse (ctrl_t ctrl, const char *keyspec, + LDAP *ldap_conn, char *basedn, char *filter, + char **attrs, LDAPMessage **r_message) +{ + gpg_error_t err = 0; + int l_err, l_reserr; + LDAPControl *srvctrls[2] = { NULL, NULL }; + int count; + unsigned int totalcount = 0; + LDAPControl *pagectrl = NULL; + LDAPControl **resctrls = NULL; + + /* first/next mode is used to retrieve many entries; thus we should + * use paged results. We assume first/next mode if we have a state. + * We make the paged mode non-critical so that we get at least as + * many entries the server delivers anyway. */ + if (ctrl->ks_get_state) + { + l_err = ldap_create_page_control (ldap_conn, PAGE_SIZE, + ctrl->ks_get_state->pagecookie, 0, + &pagectrl); + if (err) + { + err = ldap_err_to_gpg_err (l_err); + log_error ("ks-ldap: create_page_control failed: %s\n", + ldap_err2string (l_err)); + goto leave; + } + + ctrl->ks_get_state->more_pages = 0; + srvctrls[0] = pagectrl; + } + + npth_unprotect (); + l_err = ldap_search_ext_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, + filter, attrs, 0, + srvctrls[0]? srvctrls : NULL, NULL, NULL, 0, + r_message); + npth_protect (); + if (l_err) + { + err = ldap_err_to_gpg_err (l_err); + log_error ("ks-ldap: LDAP search error: %s\n", ldap_err2string (l_err)); + goto leave; + } + + if (ctrl->ks_get_state) + { + l_err = ldap_parse_result (ldap_conn, *r_message, &l_reserr, + NULL, NULL, NULL, &resctrls, 0); + if (l_err) + { + err = ldap_err_to_gpg_err (l_err); + log_error ("ks-ldap: LDAP parse result error: %s\n", + ldap_err2string (l_err)); + goto leave; + } + /* Get the current cookie. */ + if (ctrl->ks_get_state->pagecookie) + { + ber_bvfree (ctrl->ks_get_state->pagecookie); + ctrl->ks_get_state->pagecookie = NULL; + } + l_err = ldap_parse_page_control (ldap_conn, resctrls, + &totalcount, + &ctrl->ks_get_state->pagecookie); + if (l_err) + { + err = ldap_err_to_gpg_err (l_err); + log_error ("ks-ldap: LDAP parse page control error: %s\n", + ldap_err2string (l_err)); + goto leave; + } + + ctrl->ks_get_state->pageno++; + + /* Decide whether there will be more pages. */ + ctrl->ks_get_state->more_pages = + (ctrl->ks_get_state->pagecookie + && ctrl->ks_get_state->pagecookie->bv_val + && *ctrl->ks_get_state->pagecookie->bv_val); + + srvctrls[0] = NULL; + } + + count = ldap_count_entries (ldap_conn, *r_message); + if (ctrl->ks_get_state) + { + if (count >= 0) + ctrl->ks_get_state->total += count; + if (opt.verbose) + log_info ("ks-ldap: received result page %u%s (%d/%u/%u)\n", + ctrl->ks_get_state->pageno, + ctrl->ks_get_state->more_pages? "":" (last)", + count, ctrl->ks_get_state->total, totalcount); + } + if (count < 1) + { + if (!ctrl->ks_get_state || ctrl->ks_get_state->pageno == 1) + log_info ("ks-ldap: key %s not found on keyserver\n", keyspec); + + if (count == -1) + err = ldap_to_gpg_err (ldap_conn); + else + err = gpg_error (GPG_ERR_NO_DATA); + + goto leave; + } + + + leave: + if (resctrls) + ldap_controls_free (resctrls); + if (pagectrl) + ldap_control_free (pagectrl); + return err; +} + /* Get the key described key the KEYSPEC string from the keyserver * identified by URI. On success R_FP has an open stream to read the @@ -1012,7 +1158,6 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, unsigned int ks_get_flags, estream_t *r_fp) { gpg_error_t err = 0; - int ldap_err; unsigned int serverinfo; char *host = NULL; int use_tls; @@ -1020,12 +1165,12 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, LDAP *ldap_conn = NULL; char *basedn = NULL; estream_t fp = NULL; - int count; LDAPMessage *message = NULL; LDAPMessage *msg; int anykey = 0; int first_mode = 0; int next_mode = 0; + int get_first; strlist_t seen = NULL; /* The set of entries that we've seen. */ /* The ordering is significant. Specifically, "pgpcertid" needs to * be the second item in the list, since everything after it may be @@ -1077,13 +1222,41 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, if (next_mode) { + next_again: + if (!ctrl->ks_get_state->msg_iter && ctrl->ks_get_state->more_pages) + { + /* Get the next page of results. */ + if (ctrl->ks_get_state->message) + { + ldap_msgfree (ctrl->ks_get_state->message); + ctrl->ks_get_state->message = NULL; + } + attrs[0] = ((ctrl->ks_get_state->serverinfo & SERVERINFO_PGPKEYV2)? + "pgpKeyV2" : "pgpKey"); + err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec, + ctrl->ks_get_state->ldap_conn, + ctrl->ks_get_state->basedn, + ctrl->ks_get_state->filter, + attrs, + &ctrl->ks_get_state->message); + if (err) + goto leave; + ctrl->ks_get_state->msg_iter = ctrl->ks_get_state->message; + get_first = 1; + } + else + get_first = 0; + while (ctrl->ks_get_state->msg_iter) { npth_unprotect (); ctrl->ks_get_state->msg_iter - = ldap_next_entry (ctrl->ks_get_state->ldap_conn, - 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, @@ -1093,13 +1266,19 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, if (!err) break; /* Found. */ else if (gpg_err_code (err) == GPG_ERR_NO_DATA) - err = 0; /* Skip empty/duplicate attributes. */ + err = 0; /* Skip empty attributes. */ else goto leave; } } + if (!ctrl->ks_get_state->msg_iter || !fp) - err = gpg_error (GPG_ERR_NO_DATA); + { + 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. */ @@ -1116,7 +1295,13 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, /* 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 (keyspec, &filter, 1, serverinfo); + 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; @@ -1126,31 +1311,11 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, /* Replace "dummy". */ attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey"; - npth_unprotect (); - ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, - filter, attrs, 0, &message); - npth_protect (); - if (ldap_err) - { - err = ldap_err_to_gpg_err (ldap_err); + err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, filter, attrs, + &message); + if (err) + goto leave; - log_error ("ks-ldap: LDAP search error: %s\n", - ldap_err2string (ldap_err)); - goto leave; - } - - count = ldap_count_entries (ldap_conn, message); - if (count < 1) - { - log_info ("ks-ldap: key %s not found on keyserver\n", keyspec); - - if (count == -1) - err = ldap_to_gpg_err (ldap_conn); - else - err = gpg_error (GPG_ERR_NO_DATA); - - goto leave; - } for (npth_unprotect (), msg = ldap_first_entry (ldap_conn, message), @@ -1198,6 +1363,11 @@ 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->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)) { @@ -1210,15 +1380,11 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, 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; } diff --git a/dirmngr/server.c b/dirmngr/server.c index c97804277..04ebfd317 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -2585,6 +2585,17 @@ cmd_ks_get (assuan_context_t ctx, char *line) goto leave; } + if (!list && (flags & KS_GET_FLAG_FIRST)) + { + /* Need to add a dummy pattern if no pattern is given. */ + if (!add_to_strlist_try (&list, "")) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + if ((flags & KS_GET_FLAG_NEXT)) { if (list || (flags & ~KS_GET_FLAG_NEXT))