diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index 9f2157c0d..e7591b998 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -187,8 +187,10 @@ struct cert_ref_s }; typedef struct cert_ref_s *cert_ref_t; +/* Forward reference; access only via ks-engine-ldap.c. */ +struct ks_engine_ldap_local_s; -/* Forward references; access only through server.c. */ +/* Forward reference; access only through server.c. */ struct server_local_s; #if SIZEOF_UNSIGNED_LONG == 8 @@ -205,6 +207,7 @@ struct server_control_s int no_server; /* We are not running under server control. */ int status_fd; /* Only for non-server mode. */ struct server_local_s *server_local; + struct ks_engine_ldap_local_s *ks_get_state; int force_crl_refresh; /* Always load a fresh CRL. */ int check_revocations_nest_level; /* Internal to check_revovations. */ diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index c5374a996..7de4a3b95 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -243,7 +243,7 @@ ks_action_search (ctrl_t ctrl, uri_item_t keyservers, keyservers and write the result to the provided output stream. */ gpg_error_t ks_action_get (ctrl_t ctrl, uri_item_t keyservers, - strlist_t patterns, int ldap_only, estream_t outfp) + strlist_t patterns, unsigned int ks_get_flags, estream_t outfp) { gpg_error_t err = 0; gpg_error_t first_err = 0; @@ -270,7 +270,7 @@ ks_action_get (ctrl_t ctrl, uri_item_t keyservers, || strcmp (uri->parsed_uri->scheme, "https") == 0); int is_ldap = 0; - if (ldap_only) + if ((ks_get_flags & KS_GET_FLAG_ONLY_LDAP)) is_hkp_s = is_http_s = 0; #if USE_LDAP @@ -287,7 +287,8 @@ ks_action_get (ctrl_t ctrl, uri_item_t keyservers, { #if USE_LDAP if (is_ldap) - err = ks_ldap_get (ctrl, uri->parsed_uri, sl->d, &infp); + err = ks_ldap_get (ctrl, uri->parsed_uri, sl->d, ks_get_flags, + &infp); else #endif if (is_hkp_s) diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h index 36e0cf05e..e780fc7db 100644 --- a/dirmngr/ks-action.h +++ b/dirmngr/ks-action.h @@ -26,7 +26,8 @@ gpg_error_t ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers); gpg_error_t ks_action_search (ctrl_t ctrl, uri_item_t keyservers, strlist_t patterns, estream_t outfp); gpg_error_t ks_action_get (ctrl_t ctrl, uri_item_t keyservers, - strlist_t patterns, int ldap_only, estream_t outfp); + strlist_t patterns, unsigned int ks_get_flags, + estream_t outfp); gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp); gpg_error_t ks_action_put (ctrl_t ctrl, uri_item_t keyservers, void *data, size_t datalen, diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index 2432e2480..e225b67b7 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -40,7 +40,7 @@ /* Flags with infos from the connected server. */ #define SERVERINFO_REALLDAP 1 /* This is not the PGP keyserver. */ -#define SERVERINFO_PGPKEYV2 2 /* Needs "pgpeyV2" instead of "pgpKey" */ +#define SERVERINFO_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. */ @@ -50,6 +50,17 @@ time_t timegm(struct tm *tm); #endif +/* Object to keep state pertaining to this module. */ +struct ks_engine_ldap_local_s +{ + LDAP *ldap_conn; + LDAPMessage *message; + LDAPMessage *msg_iter; /* Iterator for message. */ + unsigned int serverinfo; +}; + + + static time_t ldap2epochtime (const char *timestr) @@ -165,6 +176,45 @@ ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri) } + +/* Create a new empty state object. Returns NULL on error */ +static struct ks_engine_ldap_local_s * +ks_ldap_new_state (void) +{ + return xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s)); +} + + +/* Clear the state object STATE. Returns the STATE object. */ +static struct ks_engine_ldap_local_s * +ks_ldap_clear_state (struct ks_engine_ldap_local_s *state) +{ + if (state->ldap_conn) + { + ldap_unbind (state->ldap_conn); + state->ldap_conn = NULL; + } + if (state->message) + { + ldap_msgfree (state->message); + state->message = NULL; + } + state->serverinfo = 0; + return state; +} + + +/* Release a state object. */ +void +ks_ldap_free_state (struct ks_engine_ldap_local_s *state) +{ + if (!state) + return; + ks_ldap_clear_state (state); + xfree (state); +} + + /* Convert a keyspec to a filter. Return an error if the keyspec is bad or is not supported. The filter is escaped and returned in @@ -288,6 +338,8 @@ keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact, } + +/* Helper for my_ldap_connect. */ static char * interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search, unsigned int *r_serverinfo) @@ -874,12 +926,90 @@ no_ldap_due_to_tor (ctrl_t ctrl) } +/* 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; +} + + + /* 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, estream_t *r_fp) { gpg_error_t err = 0; int ldap_err; @@ -890,7 +1020,24 @@ 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; + strlist_t seen = NULL; /* The set of entries that we've seen. */ + /* The ordering is significant. Specifically, "pgpcertid" needs to + * be the second item in the list, since everything after it may be + * discarded if we aren't in verbose mode. */ + char *attrs[] = + { + "dummy", /* (to be be replaced.) */ + "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled", + "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype", + "gpgfingerprint", + NULL + }; (void) ctrl; @@ -899,143 +1046,138 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, 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, &host, &use_tls, &serverinfo); - if (err || !basedn) + /* Make sure we got a state. */ + if ((ks_get_flags & KS_GET_FLAG_FIRST)) { - if (!err) - err = gpg_error (GPG_ERR_GENERAL); - goto out; + 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; } - /* 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 (err) - goto out; - - if (opt.debug) - log_debug ("ks-ldap: using filter: %s\n", filter); - - { - /* 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", - "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled", - "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype", - "gpgfingerprint", - NULL - }; - /* 1 if we want just attribute types; 0 if we want both attribute - * types and values. */ - int attrsonly = 0; - int count; - - /* Replace "dummy". */ - attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey"; - - npth_unprotect (); - ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, - filter, attrs, attrsonly, &message); - npth_protect (); - if (ldap_err) - { - err = ldap_err_to_gpg_err (ldap_err); - - log_error ("ks-ldap: LDAP search error: %s\n", - ldap_err2string (ldap_err)); - goto out; - } - - 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 out; - } - + if ((ks_get_flags & KS_GET_FLAG_NEXT)) { - /* 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). */ + 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; + } - /* The set of entries that we've seen. */ - strlist_t seen = NULL; - LDAPMessage *each; - int anykey = 0; + /* Do not keep an old state around if not needed. */ + if (!(first_mode || next_mode)) + { + ks_ldap_free_state (ctrl->ks_get_state); + ctrl->ks_get_state = NULL; + } + + + if (next_mode) + { + 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); + npth_protect (); + 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/duplicate attributes. */ + else + goto leave; + } + } + if (!ctrl->ks_get_state->msg_iter || !fp) + err = gpg_error (GPG_ERR_NO_DATA); + + } + else /* Not in --next mode. */ + { + /* Make sure we are talking to an OpenPGP LDAP server. */ + err = my_ldap_connect (uri, &ldap_conn, + &basedn, &host, &use_tls, &serverinfo); + if (err || !basedn) + { + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + /* Now that we have information about the server we can construct a + * query best suited for the capabilities of the server. */ + err = keyspec_to_ldap_filter (keyspec, &filter, 1, serverinfo); + if (err) + goto leave; + + if (opt.debug) + log_debug ("ks-ldap: using filter: %s\n", filter); + + /* Replace "dummy". */ + attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey"; + + 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); + + 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 (), - each = ldap_first_entry (ldap_conn, message), + msg = ldap_first_entry (ldap_conn, message), npth_protect (); - each; + msg; npth_unprotect (), - each = ldap_next_entry (ldap_conn, each), + 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, attrs[0]); - if (! vals) - { - err = ldap_to_gpg_err (ldap_conn); - log_error("ks-ldap: 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); - anykey = 1; - } - } - } - - my_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) @@ -1043,9 +1185,27 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, use_tls? "ldaps" : "ldap", host? host:""); } - } - out: + + 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; + } + 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); @@ -1062,6 +1222,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, *r_fp = fp; } + free_strlist (seen); xfree (basedn); xfree (host); diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h index d28c6ab71..be4e27e6f 100644 --- a/dirmngr/ks-engine.h +++ b/dirmngr/ks-engine.h @@ -23,6 +23,12 @@ #include "http.h" +/* Flags for engine functions. */ +#define KS_GET_FLAG_ONLY_LDAP 1 +#define KS_GET_FLAG_FIRST 2 +#define KS_GET_FLAG_NEXT 4 + + /*-- ks-action.c --*/ gpg_error_t ks_print_help (ctrl_t ctrl, const char *text); gpg_error_t ks_printf_help (ctrl_t ctrl, const char *format, @@ -63,10 +69,12 @@ gpg_error_t ks_kdns_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp); /*-- ks-engine-ldap.c --*/ gpg_error_t ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri); +void ks_ldap_free_state (struct ks_engine_ldap_local_s *state); gpg_error_t ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, estream_t *r_fp); gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, - const char *keyspec, estream_t *r_fp); + const char *keyspec, unsigned int ks_get_flags, + estream_t *r_fp); gpg_error_t ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, void *data, size_t datalen, void *info, size_t infolen); diff --git a/dirmngr/server.c b/dirmngr/server.c index a22a2f45c..c97804277 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -49,7 +49,7 @@ # include "ldap-wrapper.h" #endif #include "ks-action.h" -#include "ks-engine.h" /* (ks_hkp_print_hosttable) */ +#include "ks-engine.h" #if USE_LDAP # include "ldap-parse-uri.h" #endif @@ -2518,12 +2518,13 @@ cmd_ks_search (assuan_context_t ctx, char *line) static const char hlp_ks_get[] = - "KS_GET [--quick] [--ldap] {}\n" + "KS_GET [--quick] [--ldap] [--first|--next] {}\n" "\n" "Get the keys matching PATTERN from the configured OpenPGP keyservers\n" "(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n" "or an exact name indicated by the '=' prefix. Option --quick uses a\n" - "shorter timeout; --ldap will use only ldap servers"; + "shorter timeout; --ldap will use only ldap servers. With --first only\n" + "the first item is returned; --next is used to return the next item"; static gpg_error_t cmd_ks_get (assuan_context_t ctx, char *line) { @@ -2532,11 +2533,16 @@ cmd_ks_get (assuan_context_t ctx, char *line) strlist_t list, sl; char *p; estream_t outfp; - int ldap_only; + unsigned int flags = 0; if (has_option (line, "--quick")) ctrl->timeout = opt.connect_quick_timeout; - ldap_only = has_option (line, "--ldap"); + if (has_option (line, "--ldap")) + flags |= KS_GET_FLAG_ONLY_LDAP; + if (has_option (line, "--first")) + flags |= KS_GET_FLAG_FIRST; + if (has_option (line, "--next")) + flags |= KS_GET_FLAG_NEXT; line = skip_options (line); /* Break the line into a strlist. Each pattern is by @@ -2565,6 +2571,36 @@ cmd_ks_get (assuan_context_t ctx, char *line) } } + if ((flags & KS_GET_FLAG_FIRST) && !(flags & KS_GET_FLAG_ONLY_LDAP)) + { + err = PARM_ERROR ("--first is only supported with --ldap"); + goto leave; + } + + if (list && list->next && (flags & KS_GET_FLAG_FIRST)) + { + /* ks_action_get loops over the pattern and we can't easily keep + * this state. */ + err = PARM_ERROR ("Only one pattern allowed with --first"); + goto leave; + } + + if ((flags & KS_GET_FLAG_NEXT)) + { + if (list || (flags & ~KS_GET_FLAG_NEXT)) + { + err = PARM_ERROR ("No pattern or other options allowed with --next"); + goto leave; + } + /* Add a dummy pattern. */ + if (!add_to_strlist_try (&list, "")) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + err = ensure_keyserver (ctrl); if (err) goto leave; @@ -2579,7 +2615,7 @@ cmd_ks_get (assuan_context_t ctx, char *line) ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; err = ks_action_get (ctrl, ctrl->server_local->keyservers, - list, ldap_only, outfp); + list, flags, outfp); es_fclose (outfp); ctrl->server_local->inhibit_data_logging = 0; } @@ -3090,6 +3126,8 @@ start_command_handler (assuan_fd_t fd, unsigned int session_id) ctrl->refcount); else { + ks_ldap_free_state (ctrl->ks_get_state); + ctrl->ks_get_state = NULL; release_ctrl_ocsp_certs (ctrl); xfree (ctrl->server_local); dirmngr_deinit_default_ctrl (ctrl);