From 1324dc3490b02c4ff818655db1474c594f04e4ec Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 25 Nov 2022 16:04:54 +0100 Subject: [PATCH] gpg: New option --list-filter * g10/gpg.c (oListFilter): New. (opts): Add --list-filter. (main): Parse oListFilter. * g10/keylist.c: Include init.h and recsel.h. (struct list_filter_s, list_filter): New. (release_list_filter): New. (cleanup_keylist_globals): New. (parse_and_set_list_filter): New. (list_keyblock): Implement --list-filter type "select". * g10/import.c (impex_filter_getval): Add scope support and new property names "key-size", "algostr", "origin", "lastupd", and "url". -- This option is pretty useful to select keys based on their properties. The scope thing can be sued to limit a selection to just the primary key or to subkeys. For example: gpg -k --list-filter 'select=revoked-f && sub/algostr=ed25519' Lists all non-revoked keys with an ed25519 (signing)-subkey. --- doc/gpg.texi | 41 ++++++++++++++++++++++-- g10/gpg.c | 7 +++++ g10/gpgv.c | 8 +++++ g10/import.c | 71 ++++++++++++++++++++++++++++++++++++----- g10/keylist.c | 82 +++++++++++++++++++++++++++++++++++++++++++++--- g10/main.h | 1 + g10/test-stubs.c | 8 +++++ 7 files changed, 204 insertions(+), 14 deletions(-) diff --git a/doc/gpg.texi b/doc/gpg.texi index 25065f8e4..ed75a613a 100644 --- a/doc/gpg.texi +++ b/doc/gpg.texi @@ -1338,6 +1338,13 @@ Assume "yes" on most questions. Should not be used in an option file. Assume "no" on most questions. Should not be used in an option file. +@item --list-filter @{select=@var{expr}@} +@opindex list-filter +A list filter can be used to output only certain keys during key +listsin command. For the availbale property names, see the description +of @option{--import-filter}. + + @item --list-options @var{parameters} @opindex list-options This is a space or comma delimited string that gives options used when @@ -2550,11 +2557,21 @@ The available filter types are: Self-signatures are not considered. Currently only implemented for --import-filter. + @item select + This filter is only implemented by @option{--list-filter}. All + property names may be used. + @end table For the syntax of the expression see the chapter "FILTER EXPRESSIONS". The property names for the expressions depend on the actual filter -type and are indicated in the following table. +type and are indicated in the following table. Note that all property +names may also be used by @option{--list-filter}. + +Property names may be prefix with a scope delimited by a slash. Valid +scopes are "pub" for public and secret primary keys, "sub" for public +and secret subkeys, "uid" for for user-ID packets, and "sig" for +signature packets. Invalid scopes are currently ignored. The available properties are: @@ -2567,10 +2584,18 @@ The available properties are: The addr-spec part of a user id with mailbox or the empty string. (keep-uid) + @item algostr + A string with the key algorithm description. For example "rsa3072" + or "ed25519". + @item key_algo A number with the public key algorithm of a key or subkey packet. (drop-subkey) + @item key_size + A number with the effective key size of a key or subkey packet. + (drop-subkey) + @item key_created @itemx key_created_d The first is the timestamp a public key or subkey packet was @@ -2593,7 +2618,7 @@ The available properties are: been revoked. @item disabled - Boolean indicating whether a primary key is disabled. (not used) + Boolean indicating whether a primary key is disabled. @item secret Boolean indicating whether a key or subkey is a secret one. @@ -2616,6 +2641,18 @@ The available properties are: @item sig_digest_algo A number with the digest algorithm of a signature packet. (drop-sig) + @item origin + A string with the key origin or a question mark. For example the + string ``wkd'' is used if a key originated from a Web Key Directory + lookup. + + @item lastupd + The timestamp the key was last updated from a keyserver or the Web + Key Directory. + + @item url + A string with the the URL associated wit the last key lookup. + @end table @item --export-options @var{parameters} diff --git a/g10/gpg.c b/g10/gpg.c index 1514254b9..68c0454ee 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -327,6 +327,7 @@ enum cmd_and_opt_values oExportOptions, oExportFilter, oListOptions, + oListFilter, oVerifyOptions, oTempDir, oExecPath, @@ -794,6 +795,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_header ("Keylist", N_("Options controlling key listings")), ARGPARSE_s_s (oListOptions, "list-options", "@"), + ARGPARSE_s_s (oListFilter, "list-filter", "@"), ARGPARSE_s_n (oFullTimestrings, "full-timestrings", "@"), ARGPARSE_s_n (oShowPhotos, "show-photos", "@"), ARGPARSE_s_n (oNoShowPhotos, "no-show-photos", "@"), @@ -3357,6 +3359,11 @@ main (int argc, char **argv) if (rc) log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); break; + case oListFilter: + rc = parse_and_set_list_filter (pargs.r.ret_str); + if (rc) + log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); + break; case oListOptions: if(!parse_list_options(pargs.r.ret_str)) { diff --git a/g10/gpgv.c b/g10/gpgv.c index 3bb99dc6c..ceded4af9 100644 --- a/g10/gpgv.c +++ b/g10/gpgv.c @@ -812,3 +812,11 @@ get_revocation_reason (PKT_signature *sig, char **r_reason, *r_comment = NULL; return 0; } + +const char * +impex_filter_getval (void *cookie, const char *propname) +{ + (void)cookie; + (void)propname; + return NULL; +} diff --git a/g10/import.c b/g10/import.c index c3ef89323..9fab46ca6 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1430,7 +1430,8 @@ check_prefs (ctrl_t ctrl, kbnode_t keyblock) } -/* Helper for apply_*_filter in import.c and export.c. */ +/* Helper for apply_*_filter in import.c and export.c and also used by + * keylist.c. */ const char * impex_filter_getval (void *cookie, const char *propname) { @@ -1440,11 +1441,32 @@ impex_filter_getval (void *cookie, const char *propname) kbnode_t node = parm->node; static char numbuf[20]; const char *result; + const char *s; + enum { scpNone = 0, scpPub, scpSub, scpUid, scpSig} scope = 0; log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC); - if (node->pkt->pkttype == PKT_USER_ID - || node->pkt->pkttype == PKT_ATTRIBUTE) + /* We allow a prefix delimited by a slash to limit the scope of the + * keyword. Note that "pub" also includes "sec" and "sub" includes + * "ssb". */ + if ((s=strchr (propname, '/')) && s != propname) + { + size_t n = s - propname; + if (!strncmp (propname, "pub", n)) + scope = scpPub; + else if (!strncmp (propname, "sub", n)) + scope = scpSub; + else if (!strncmp (propname, "uid", n)) + scope = scpUid; + else if (!strncmp (propname, "sig", n)) + scope = scpSig; + + propname = s + 1; + } + + if ((node->pkt->pkttype == PKT_USER_ID + || node->pkt->pkttype == PKT_ATTRIBUTE) + && (!scope || scope == scpUid)) { PKT_user_id *uid = node->pkt->pkt.user_id; @@ -1473,7 +1495,8 @@ impex_filter_getval (void *cookie, const char *propname) else result = NULL; } - else if (node->pkt->pkttype == PKT_SIGNATURE) + else if (node->pkt->pkttype == PKT_SIGNATURE + && (!scope || scope == scpSig)) { PKT_signature *sig = node->pkt->pkt.signature; @@ -1503,10 +1526,12 @@ impex_filter_getval (void *cookie, const char *propname) else result = NULL; } - else if (node->pkt->pkttype == PKT_PUBLIC_KEY - || node->pkt->pkttype == PKT_SECRET_KEY - || node->pkt->pkttype == PKT_PUBLIC_SUBKEY - || node->pkt->pkttype == PKT_SECRET_SUBKEY) + else if (((node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_SECRET_KEY) + && (!scope || scope == scpPub)) + || ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + && (!scope || scope == scpSub))) { PKT_public_key *pk = node->pkt->pkt.public_key; @@ -1520,6 +1545,16 @@ impex_filter_getval (void *cookie, const char *propname) snprintf (numbuf, sizeof numbuf, "%d", pk->pubkey_algo); result = numbuf; } + else if (!strcmp (propname, "key_size")) + { + snprintf (numbuf, sizeof numbuf, "%u", nbits_from_pk (pk)); + result = numbuf; + } + else if (!strcmp (propname, "algostr")) + { + pubkey_string (pk, parm->hexfpr, sizeof parm->hexfpr); + result = parm->hexfpr; + } else if (!strcmp (propname, "key_created")) { snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->timestamp); @@ -1556,6 +1591,26 @@ impex_filter_getval (void *cookie, const char *propname) hexfingerprint (pk, parm->hexfpr, sizeof parm->hexfpr); result = parm->hexfpr; } + else if (!strcmp (propname, "origin")) + { + result = key_origin_string (pk->keyorg); + } + else if (!strcmp (propname, "lastupd")) + { + snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->keyupdate); + result = numbuf; + } + else if (!strcmp (propname, "url")) + { + if (pk->updateurl && *pk->updateurl) + { + /* Fixme: This might get truncated. */ + mem2str (parm->hexfpr, pk->updateurl, sizeof parm->hexfpr); + result = parm->hexfpr; + } + else + result = ""; + } else result = NULL; } diff --git a/g10/keylist.c b/g10/keylist.c index f8e4ff32a..1ced732a4 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -44,6 +44,8 @@ #include "../common/mbox-util.h" #include "../common/zb32.h" #include "tofu.h" +#include "../common/init.h" +#include "../common/recsel.h" #include "../common/compliance.h" #include "../common/pkscreening.h" @@ -64,16 +66,26 @@ struct keylist_context int no_validity; /* Do not show validity. */ }; - -static void list_keyblock (ctrl_t ctrl, - kbnode_t keyblock, int secret, int has_secret, - int fpr, struct keylist_context *listctx); +/* An object and a global instance to store selectors created from + * --list-filter select=EXPR. + */ +struct list_filter_s +{ + recsel_expr_t selkey; +}; +struct list_filter_s list_filter; /* The stream used to write attribute packets to. */ static estream_t attrib_fp; + + +static void list_keyblock (ctrl_t ctrl, + kbnode_t keyblock, int secret, int has_secret, + int fpr, struct keylist_context *listctx); + /* Release resources from a keylist context. */ static void keylist_context_release (struct keylist_context *listctx) @@ -82,6 +94,49 @@ keylist_context_release (struct keylist_context *listctx) } +static void +release_list_filter (struct list_filter_s *filt) +{ + recsel_release (filt->selkey); + filt->selkey = NULL; +} + + +static void +cleanup_keylist_globals (void) +{ + release_list_filter (&list_filter); +} + + +/* Parse and set an list filter from string. STRING has the format + * "NAME=EXPR" with NAME being the name of the filter. Spaces before + * and after NAME are not allowed. If this function is all called + * several times all expressions for the same NAME are concatenated. + * Supported filter names are: + * + * - select :: If the expression evaluates to true for a certain key + * this key will be listed. The expression may use any + * variable defined for the export and import filters. + * + */ +gpg_error_t +parse_and_set_list_filter (const char *string) +{ + gpg_error_t err; + + /* Auto register the cleanup function. */ + register_mem_cleanup_func (cleanup_keylist_globals); + + if (!strncmp (string, "select=", 7)) + err = recsel_parse_expr (&list_filter.selkey, string+7); + else + err = gpg_error (GPG_ERR_INV_NAME); + + return err; +} + + /* List the keys. If list is NULL, all available keys are listed. * With LOCATE_MODE set the locate algorithm is used to find a key; if * in addition NO_LOCAL is set the locate does not look into the local @@ -2163,6 +2218,7 @@ reorder_keyblock (KBNODE keyblock) do_reorder_keyblock (keyblock, 0); } + static void list_keyblock (ctrl_t ctrl, KBNODE keyblock, int secret, int has_secret, int fpr, @@ -2170,6 +2226,24 @@ list_keyblock (ctrl_t ctrl, { reorder_keyblock (keyblock); + if (list_filter.selkey) + { + int selected = 0; + struct impex_filter_parm_s parm; + parm.ctrl = ctrl; + + for (parm.node = keyblock; parm.node; parm.node = parm.node->next) + { + if (recsel_select (list_filter.selkey, impex_filter_getval, &parm)) + { + selected = 1; + break; + } + } + if (!selected) + return; /* Skip this one. */ + } + if (opt.with_colons) list_keyblock_colon (ctrl, keyblock, secret, has_secret); else if ((opt.list_options & LIST_SHOW_ONLY_FPR_MBOX)) diff --git a/g10/main.h b/g10/main.h index 2f14f374b..6d5ba77ac 100644 --- a/g10/main.h +++ b/g10/main.h @@ -464,6 +464,7 @@ void release_revocation_reason_info (struct revocation_reason_info *reason); void public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode, int no_local); void secret_key_list (ctrl_t ctrl, strlist_t list ); +gpg_error_t parse_and_set_list_filter (const char *string); void print_subpackets_colon(PKT_signature *sig); void reorder_keyblock (KBNODE keyblock); void list_keyblock_direct (ctrl_t ctrl, kbnode_t keyblock, int secret, diff --git a/g10/test-stubs.c b/g10/test-stubs.c index cfe33b1d0..6ae0f4eb7 100644 --- a/g10/test-stubs.c +++ b/g10/test-stubs.c @@ -572,3 +572,11 @@ get_revocation_reason (PKT_signature *sig, char **r_reason, *r_comment = NULL; return 0; } + +const char * +impex_filter_getval (void *cookie, const char *propname) +{ + (void)cookie; + (void)propname; + return NULL; +}