gpg: New option --import-filter
authorWerner Koch <wk@gnupg.org>
Fri, 1 Jul 2016 14:24:04 +0000 (16:24 +0200)
committerWerner Koch <wk@gnupg.org>
Fri, 1 Jul 2016 14:28:29 +0000 (16:28 +0200)
* g10/gpg.c (oImportFilter): New.
(opts): Add --import-filter.
(main): Handle option.
* g10/import.c: Include recsel.h, init.h, and mbox-util.h.
(import_keep_uid): New global var.
(cleanup_import_globals): New.
(parse_and_set_import_filter): New.
(filter_getval): New.
(apply_keep_uid_filter): New.
(import_one): Apply filter if set.
--

Funny new option.  It can for example be used to export a key with
only one user id:

  gpg --no-options --import --import-options import-export \
      --import-filter keep-uid='mbox=wk@gnupg.org'         \
     < full-key.pub > key-with-one-uid.pub

More features will eventually be added.

Signed-off-by: Werner Koch <wk@gnupg.org>
doc/gpg.texi
g10/gpg.c
g10/import.c
g10/main.h

index 6f0249a..9a06221 100644 (file)
@@ -2218,6 +2218,45 @@ opposite meaning. The options are:
   Defaults to no.
 @end table
 
+@item --import-filter @code{@var{name}=@var{expr}}
+@opindex import-filter
+This option defines an import filter which is implied to the imported
+keyblock right before it will be stored.  @var{name} defines the type
+of filter to use, @var{expr} the expression to evaluate.  The option
+can be used several times which then appends more expression to the
+same @var{name}.
+
+@noindent
+The available filter types are:
+
+@table @asis
+
+  @item keep-uid
+  This filter will keep a user id packet and its dependent packets in
+  the keyblock if the expression evaluates to true.
+
+@end table
+
+The syntax for the expression is defined in the appendix (FIXME).  The
+property names for the expressions depend on the actual filter type
+and are indicated in the following table.
+
+The available properties are:
+
+@table @asis
+
+  @item uid
+  A string with the user id.  (keep-uid)
+
+  @item mbox
+  The addr-spec part of a user id with mailbox or the empty string.
+  (keep-uid)
+
+  @item primary
+  Boolean indicating whether the user id is the primary one.  (keep-uid)
+
+@end table
+
 @item --export-options @code{parameters}
 @opindex export-options
 This is a space or comma delimited string that gives options for
index b1d6c34..009b84c 100644 (file)
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -300,6 +300,7 @@ enum cmd_and_opt_values
     oKeyServer,
     oKeyServerOptions,
     oImportOptions,
+    oImportFilter,
     oExportOptions,
     oListOptions,
     oVerifyOptions,
@@ -572,6 +573,7 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_s (oKeyServer, "keyserver", "@"),
   ARGPARSE_s_s (oKeyServerOptions, "keyserver-options", "@"),
   ARGPARSE_s_s (oImportOptions, "import-options", "@"),
+  ARGPARSE_s_s (oImportFilter,  "import-filter", "@"),
   ARGPARSE_s_s (oExportOptions, "export-options", "@"),
   ARGPARSE_s_s (oListOptions,   "list-options", "@"),
   ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"),
@@ -2033,6 +2035,7 @@ parse_tofu_db_format (const char *db_format)
     }
 }
 
+
 /* This function called to initialized a new control object.  It is
    assumed that this object has been zeroed out before calling this
    function. */
@@ -3031,6 +3034,11 @@ main (int argc, char **argv)
                  log_error(_("invalid import options\n"));
              }
            break;
+         case oImportFilter:
+           rc = parse_and_set_import_filter (pargs.r.ret_str);
+           if (rc)
+              log_error (_("invalid filter option: %s\n"), gpg_strerror (rc));
+           break;
          case oExportOptions:
            if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1))
              {
index 332e266..deb2787 100644 (file)
@@ -1,6 +1,6 @@
 /* import.c - import a key into our key storage.
  * Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc.
- * Copyright (C) 2014  Werner Koch
+ * Copyright (C) 2014, 2016  Werner Koch
  *
  * This file is part of GnuPG.
  *
 #include "i18n.h"
 #include "ttyio.h"
 #include "status.h"
+#include "recsel.h"
 #include "keyserver-internal.h"
 #include "call-agent.h"
 #include "../common/membuf.h"
+#include "../common/init.h"
+#include "../common/mbox-util.h"
+
 
 struct import_stats_s
 {
@@ -60,6 +64,16 @@ struct import_stats_s
 };
 
 
+/* A global variable to store the selector created from
+ * --import-filter keep-uid=EXPR.
+ *
+ * FIXME: We should put this into the CTRL object but that requires a
+ * lot more changes right now.
+ */
+static recsel_expr_t import_keep_uid;
+
+
+
 static int import (ctrl_t ctrl,
                    IOBUF inp, const char* fname, struct import_stats_s *stats,
                   unsigned char **fpr, size_t *fpr_len, unsigned int options,
@@ -95,6 +109,16 @@ static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs,
 static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs,
                             const char *fname, u32 *keyid );
 
+
+\f
+static void
+cleanup_import_globals (void)
+{
+  recsel_release (import_keep_uid);
+  import_keep_uid = NULL;
+}
+
+
 int
 parse_import_options(char *str,unsigned int *options,int noisy)
 {
@@ -143,6 +167,39 @@ parse_import_options(char *str,unsigned int *options,int noisy)
 }
 
 
+/* Parse and set an import 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:
+ *
+ *  - keep-uid :: If the expression evaluates to true for a certain
+ *                user ID packet, that packet and all it dependencies
+ *                will be imported.  The expression may use these
+ *                variables:
+ *
+ *                - uid  :: The entire user ID.
+ *                - mbox :: The mail box part of the user ID.
+ *                - primary :: Evaluate to true for the primary user ID.
+ */
+gpg_error_t
+parse_and_set_import_filter (const char *string)
+{
+  gpg_error_t err;
+
+  /* Auto register the cleanup function.  */
+  register_mem_cleanup_func (cleanup_import_globals);
+
+  if (!strncmp (string, "keep-uid=", 9))
+    err = recsel_parse_expr (&import_keep_uid, string+9);
+  else
+    err = gpg_error (GPG_ERR_INV_NAME);
+
+  return err;
+}
+
+
+
 import_stats_t
 import_new_stats_handle (void)
 {
@@ -983,6 +1040,74 @@ check_prefs (ctrl_t ctrl, kbnode_t keyblock)
 }
 
 
+/* Helper for apply_keep_uid_filter.  */
+static const char *
+filter_getval (void *cookie, const char *propname)
+{
+  kbnode_t node = cookie;
+  const char *result;
+
+  if (node->pkt->pkttype == PKT_USER_ID)
+    {
+      if (!strcmp (propname, "uid"))
+        result = node->pkt->pkt.user_id->name;
+      else if (!strcmp (propname, "mbox"))
+        {
+          if (!node->pkt->pkt.user_id->mbox)
+            {
+              node->pkt->pkt.user_id->mbox
+                = mailbox_from_userid (node->pkt->pkt.user_id->name);
+            }
+          return node->pkt->pkt.user_id->mbox;
+        }
+      else if (!strcmp (propname, "primary"))
+        result = node->pkt->pkt.user_id->is_primary? "1":"0";
+      else
+        result = NULL;
+    }
+  else
+    result = NULL;
+
+  return result;
+}
+
+/*
+ * Apply the keep-uid filter to the keyblock.  The deleted nodes are
+ * marked and thus the caller should call commit_kbnode afterwards.
+ * KEYBLOCK must not have any blocks marked as deleted.
+ */
+static void
+apply_keep_uid_filter (kbnode_t keyblock, recsel_expr_t selector)
+{
+  kbnode_t node;
+
+  for (node = keyblock->next; node; node = node->next )
+    {
+      if (node->pkt->pkttype == PKT_USER_ID)
+        {
+          if (!recsel_select (selector, filter_getval, node))
+            {
+
+              /* log_debug ("keep-uid: deleting '%s'\n", */
+              /*            node->pkt->pkt.user_id->name); */
+              /* The UID packet and all following packets up to the
+               * next UID or a subkey.  */
+              delete_kbnode (node);
+              for (; node->next
+                     && node->next->pkt->pkttype != PKT_USER_ID
+                     && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
+                     && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
+                   node = node->next)
+                delete_kbnode (node->next);
+           }
+          /* else */
+          /*   log_debug ("keep-uid: keeping '%s'\n", */
+          /*              node->pkt->pkt.user_id->name); */
+        }
+    }
+}
+
+
 /*
  * Try to import one keyblock. Return an error only in serious cases,
  * but never for an invalid keyblock.  It uses log_error to increase
@@ -1116,6 +1241,14 @@ import_one (ctrl_t ctrl,
   /* Get rid of deleted nodes.  */
   commit_kbnode (&keyblock);
 
+  /* Apply import filter.  */
+  if (import_keep_uid)
+    {
+      apply_keep_uid_filter (keyblock, import_keep_uid);
+      commit_kbnode (&keyblock);
+    }
+
+
   /* Show the key in the form it is merged or inserted.  We skip this
    * if "import-export" is also active without --armor or the output
    * file has explicily been given. */
index 322f43c..58f2a73 100644 (file)
@@ -349,6 +349,7 @@ typedef struct import_stats_s *import_stats_t;
 typedef gpg_error_t (*import_screener_t)(kbnode_t keyblock, void *arg);
 
 int parse_import_options(char *str,unsigned int *options,int noisy);
+gpg_error_t parse_and_set_import_filter (const char *string);
 void import_keys (ctrl_t ctrl, char **fnames, int nnames,
                  import_stats_t stats_hd, unsigned int options);
 int import_keys_stream (ctrl_t ctrl, iobuf_t inp, import_stats_t stats_hd,