gpg: New option --export-filter
authorWerner Koch <wk@gnupg.org>
Fri, 1 Jul 2016 14:50:12 +0000 (16:50 +0200)
committerWerner Koch <wk@gnupg.org>
Fri, 1 Jul 2016 14:50:12 +0000 (16:50 +0200)
* g10/gpg.c (oExportFilter): New.
(opts): Add --export-filter.
(main): Handle option.
* g10/export.c: Include recsel.h, init.h, and mbox-util.h.
(export_keep_uid): New global var.
(cleanup_export_globals): New.
(parse_and_set_export_filter): New.
(filter_getval): New.
(apply_keep_uid_filter): New.
(do_export_stream): Apply filter if set.

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

index 9a06221..7118e0d 100644 (file)
@@ -2219,12 +2219,14 @@ opposite meaning. The options are:
 @end table
 
 @item --import-filter @code{@var{name}=@var{expr}}
+@itemx --export-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}.
+@opindex export-filter
+These options define an import/export filter which are applied to the
+imported/exported keyblock right before it will be stored/written.
+@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:
index 4137235..2a50b32 100644 (file)
@@ -35,6 +35,9 @@
 #include "i18n.h"
 #include "membuf.h"
 #include "host2net.h"
+#include "recsel.h"
+#include "mbox-util.h"
+#include "init.h"
 #include "trustdb.h"
 #include "call-agent.h"
 
@@ -56,6 +59,16 @@ struct export_stats_s
 };
 
 
+/* A global variable to store the selector created from
+ * --export-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 export_keep_uid;
+
+
+
 /* Local prototypes.  */
 static int do_export (ctrl_t ctrl, strlist_t users, int secret,
                       unsigned int options, export_stats_t stats);
@@ -65,6 +78,12 @@ static int do_export_stream (ctrl_t ctrl, iobuf_t out,
                             export_stats_t stats, int *any);
 
 \f
+static void
+cleanup_export_globals (void)
+{
+  recsel_release (export_keep_uid);
+  export_keep_uid = NULL;
+}
 
 
 /* Option parser for export options.  See parse_options fro
@@ -100,6 +119,38 @@ parse_export_options(char *str,unsigned int *options,int noisy)
 }
 
 
+/* Parse and set an export 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 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 exported.  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_export_filter (const char *string)
+{
+  gpg_error_t err;
+
+  /* Auto register the cleanup function.  */
+  register_mem_cleanup_func (cleanup_export_globals);
+
+  if (!strncmp (string, "keep-uid=", 9))
+    err = recsel_parse_expr (&export_keep_uid, string+9);
+  else
+    err = gpg_error (GPG_ERR_INV_NAME);
+
+  return err;
+}
+
+
 /* Create a new export stats object initialized to zero.  On error
    returns NULL and sets ERRNO.  */
 export_stats_t
@@ -1147,6 +1198,74 @@ receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
 }
 
 
+/* 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);
+        }
+    }
+}
+
+
 /* Export the keys identified by the list of strings in USERS to the
    stream OUT.  If Secret is false public keys will be exported.  With
    secret true secret keys will be exported; in this case 1 means the
@@ -1326,6 +1445,13 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
       if ((options & EXPORT_CLEAN))
         clean_key (keyblock, opt.verbose, (options&EXPORT_MINIMAL), NULL, NULL);
 
+      if (export_keep_uid)
+        {
+          commit_kbnode (&keyblock);
+          apply_keep_uid_filter (keyblock, export_keep_uid);
+          commit_kbnode (&keyblock);
+        }
+
       /* And write it. */
       xfree (cache_nonce);
       cache_nonce = NULL;
index 009b84c..154d39a 100644 (file)
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -302,6 +302,7 @@ enum cmd_and_opt_values
     oImportOptions,
     oImportFilter,
     oExportOptions,
+    oExportFilter,
     oListOptions,
     oVerifyOptions,
     oTempDir,
@@ -575,6 +576,7 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_s (oImportOptions, "import-options", "@"),
   ARGPARSE_s_s (oImportFilter,  "import-filter", "@"),
   ARGPARSE_s_s (oExportOptions, "export-options", "@"),
+  ARGPARSE_s_s (oExportFilter,  "export-filter", "@"),
   ARGPARSE_s_s (oListOptions,   "list-options", "@"),
   ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"),
 
@@ -3049,6 +3051,11 @@ main (int argc, char **argv)
                  log_error(_("invalid export options\n"));
              }
            break;
+         case oExportFilter:
+           rc = parse_and_set_export_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))
              {
index 58f2a73..3ee2762 100644 (file)
@@ -379,6 +379,7 @@ void export_release_stats (export_stats_t stats);
 void export_print_stats (export_stats_t stats);
 
 int parse_export_options(char *str,unsigned int *options,int noisy);
+gpg_error_t parse_and_set_export_filter (const char *string);
 
 int export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
                     export_stats_t stats);