dirmngr: New options --first and --next for KS_GET.

* dirmngr/server.c (cmd_ks_get): Add option --first and --next.
(start_command_handler): Free that new ldap state.
* dirmngr/ks-engine-ldap.c (struct ks_engine_ldap_local_s): New.
(ks_ldap_new_state, ks_ldap_clear_state): New.
(ks_ldap_free_state): New.
(return_one_keyblock): New.  Mostly factored out from ....
(ks_ldap_get): here.  Implement --first/--next feature.

* dirmngr/ks-action.c (ks_action_get): Rename arg ldap_only to
ks_get_flags.
* dirmngr/ks-engine.h (KS_GET_FLAG_ONLY_LDAP): New.
(KS_GET_FLAG_FIRST): New.
(KS_GET_FLAG_NEXT): New.

* dirmngr/dirmngr.h (struct server_control_s): Add member
ks_get_state.
(struct ks_engine_ldap_local_s): New forward reference.
--

This feature allows to fetch keyblock by keyblock from an LDAP server.
This way tools can process and maybe filter each keyblock in a more
flexible way.  Here is an example where two keyblocks for one mail
address are returned:

  $ gpg-connect-agent --dirmngr
  > ks_get --ldap --first  <foo@example.org>
  [... First keyblock is returned ]
  OK
  > ks_get --next
  [ ... Next keyblock is returned ]
  OK
  > ks_get --next
  ERR 167772218 No data <Dirmngr>

GnuPG_bug_id: 6224
This commit is contained in:
Werner Koch 2022-10-04 12:44:29 +02:00
parent 3390951ffd
commit 4de98d4468
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
6 changed files with 353 additions and 141 deletions

View File

@ -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. */

View File

@ -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)

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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] {<pattern>}\n"
"KS_GET [--quick] [--ldap] [--first|--next] {<pattern>}\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);