g10: Fix use-after-free.
[gnupg.git] / g10 / export.c
index 3ce8185..6a5597c 100644 (file)
@@ -16,7 +16,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
@@ -62,11 +62,13 @@ struct export_stats_s
 
 /* A global variable to store the selector created from
  * --export-filter keep-uid=EXPR.
+ * --export-filter drop-subkey=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;
+static recsel_expr_t export_drop_subkey;
 
 
 
@@ -77,6 +79,10 @@ static int do_export_stream (ctrl_t ctrl, iobuf_t out,
                              strlist_t users, int secret,
                              kbnode_t *keyblock_out, unsigned int options,
                             export_stats_t stats, int *any);
+static gpg_error_t print_pka_or_dane_records
+/**/                 (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
+                      const void *data, size_t datalen,
+                      int print_pka, int print_dane);
 
 \f
 static void
@@ -84,6 +90,8 @@ cleanup_export_globals (void)
 {
   recsel_release (export_keep_uid);
   export_keep_uid = NULL;
+  recsel_release (export_drop_subkey);
+  export_drop_subkey = NULL;
 }
 
 
@@ -138,6 +146,14 @@ parse_export_options(char *str,unsigned int *options,int noisy)
  *                - uid  :: The entire user ID.
  *                - mbox :: The mail box part of the user ID.
  *                - primary :: Evaluate to true for the primary user ID.
+ *
+ *  - drop-subkey :: If the expression evaluates to true for a subkey
+ *                packet that subkey and all it dependencies will be
+ *                remove from the keyblock.  The expression may use these
+ *                variables:
+ *
+ *                - secret   :: 1 for a secret subkey, else 0.
+ *                - key_algo :: Public key algorithm id
  */
 gpg_error_t
 parse_and_set_export_filter (const char *string)
@@ -149,6 +165,8 @@ parse_and_set_export_filter (const char *string)
 
   if (!strncmp (string, "keep-uid=", 9))
     err = recsel_parse_expr (&export_keep_uid, string+9);
+  else if (!strncmp (string, "drop-subkey=", 12))
+    err = recsel_parse_expr (&export_drop_subkey, string+12);
   else
     err = gpg_error (GPG_ERR_INV_NAME);
 
@@ -1204,15 +1222,19 @@ receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
 
 
 /* Write KEYBLOCK either to stdout or to the file set with the
- * --output option.  */
+ * --output option.  This is a simplified version of do_export_stream
+ * which supports only a few export options.  */
 gpg_error_t
-write_keyblock_to_output (kbnode_t keyblock, int with_armor)
+write_keyblock_to_output (kbnode_t keyblock, int with_armor,
+                          unsigned int options)
 {
   gpg_error_t err;
   const char *fname;
   iobuf_t out;
   kbnode_t node;
   armor_filter_context_t *afx = NULL;
+  iobuf_t out_help = NULL;
+  PKT_public_key *pk = NULL;
 
   fname = opt.outfile? opt.outfile : "-";
   if (is_secured_filename (fname) )
@@ -1228,6 +1250,12 @@ write_keyblock_to_output (kbnode_t keyblock, int with_armor)
   if (opt.verbose)
     log_info (_("writing to '%s'\n"), iobuf_get_fname_nonnull (out));
 
+  if ((options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT)))
+    {
+      with_armor = 0;
+      out_help = iobuf_temp ();
+    }
+
   if (with_armor)
     {
       afx = new_armor_context ();
@@ -1237,60 +1265,48 @@ write_keyblock_to_output (kbnode_t keyblock, int with_armor)
 
   for (node = keyblock; node; node = node->next)
     {
-      if (!is_deleted_kbnode (node) && node->pkt->pkttype != PKT_RING_TRUST)
-       {
-         err = build_packet (out, node->pkt);
-         if (err)
-           {
-             log_error ("build_packet(%d) failed: %s\n",
-                        node->pkt->pkttype, gpg_strerror (err) );
-             goto leave;
-           }
-       }
+      if (is_deleted_kbnode (node) || node->pkt->pkttype == PKT_RING_TRUST)
+        continue;
+      if (!pk && (node->pkt->pkttype == PKT_PUBLIC_KEY
+                  || node->pkt->pkttype == PKT_SECRET_KEY))
+        pk = node->pkt->pkt.public_key;
+
+      err = build_packet (out_help? out_help : out, node->pkt);
+      if (err)
+        {
+          log_error ("build_packet(%d) failed: %s\n",
+                     node->pkt->pkttype, gpg_strerror (err) );
+          goto leave;
+        }
     }
   err = 0;
 
+  if (out_help && pk)
+    {
+      const void *data;
+      size_t datalen;
+
+      iobuf_flush_temp (out_help);
+      data = iobuf_get_temp_buffer (out_help);
+      datalen = iobuf_get_temp_length (out_help);
+
+      err = print_pka_or_dane_records (out,
+                                       keyblock, pk, data, datalen,
+                                       (options & EXPORT_PKA_FORMAT),
+                                       (options & EXPORT_DANE_FORMAT));
+    }
+
  leave:
   if (err)
     iobuf_cancel (out);
   else
     iobuf_close (out);
+  iobuf_cancel (out_help);
   release_armor_context (afx);
   return err;
 }
 
 
-/* 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.
@@ -1305,7 +1321,7 @@ apply_keep_uid_filter (kbnode_t keyblock, recsel_expr_t selector)
     {
       if (node->pkt->pkttype == PKT_USER_ID)
         {
-          if (!recsel_select (selector, filter_getval, node))
+          if (!recsel_select (selector, impex_filter_getval, node))
             {
               /* log_debug ("keep-uid: deleting '%s'\n", */
               /*            node->pkt->pkt.user_id->name); */
@@ -1327,12 +1343,44 @@ apply_keep_uid_filter (kbnode_t keyblock, recsel_expr_t selector)
 }
 
 
-/* Print DANE or PKA records for all user IDs in KEYBLOCK to the
- * stream FP.  The data for the record is taken from HEXDATA.  HEXFPR
- * is the fingerprint of the primary key.  */
+/*
+ * Apply the drop-subkey 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_drop_subkey_filter (kbnode_t keyblock, recsel_expr_t selector)
+{
+  kbnode_t node;
+
+  for (node = keyblock->next; node; node = node->next )
+    {
+      if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
+          || node->pkt->pkttype == PKT_SECRET_SUBKEY)
+        {
+          if (recsel_select (selector, impex_filter_getval, node))
+            {
+              log_debug ("drop-subkey: deleting a key\n");
+              /* The subkey packet and all following packets up to the
+               * next subkey.  */
+              delete_kbnode (node);
+              for (; node->next
+                     && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
+                     && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
+                   node = node->next)
+                delete_kbnode (node->next);
+           }
+        }
+    }
+}
+
+
+/* Print DANE or PKA records for all user IDs in KEYBLOCK to OUT.  The
+ * data for the record is taken from (DATA,DATELEN).  PK is the public
+ * key packet with the primary key. */
 static gpg_error_t
-print_pka_or_dane_records (kbnode_t keyblock, const char *hexdata,
-                           const char *hexfpr, estream_t fp,
+print_pka_or_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
+                           const void *data, size_t datalen,
                            int print_pka, int print_dane)
 {
   gpg_error_t err = 0;
@@ -1344,6 +1392,24 @@ print_pka_or_dane_records (kbnode_t keyblock, const char *hexdata,
   char *domain;
   const char *s;
   unsigned int len;
+  estream_t fp = NULL;
+  char *hexdata = NULL;
+  char *hexfpr;
+
+  hexfpr = hexfingerprint (pk, NULL, 0);
+  hexdata = bin2hex (data, datalen, NULL);
+  if (!hexdata)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  ascii_strlwr (hexdata);
+  fp = es_fopenmem (0, "rw,samethread");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
 
   for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
     {
@@ -1407,9 +1473,28 @@ print_pka_or_dane_records (kbnode_t keyblock, const char *hexdata,
         }
     }
 
+  /* Make sure it is a string and write it.  */
+  es_fputc (0, fp);
+  {
+    void *vp;
+
+    if (es_fclose_snatch (fp, &vp, NULL))
+      {
+        err = gpg_error_from_syserror ();
+        goto leave;
+      }
+    fp = NULL;
+    iobuf_writestr (out, vp);
+    es_free (vp);
+  }
+  err = 0;
+
  leave:
   xfree (hash);
   xfree (mbox);
+  es_fclose (fp);
+  xfree (hexdata);
+  xfree (hexfpr);
   return err;
 }
 
@@ -1855,7 +1940,7 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
               continue;
             }
 
-          /* The agent does not yet allow to export v3 packets.  It is
+          /* The agent does not yet allow export of v3 packets.  It is
              actually questionable whether we should allow them at
              all.  */
           if (pk->version == 3)
@@ -1883,6 +1968,13 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
           commit_kbnode (&keyblock);
         }
 
+      if (export_drop_subkey)
+        {
+          commit_kbnode (&keyblock);
+          apply_drop_subkey_filter (keyblock, export_drop_subkey);
+          commit_kbnode (&keyblock);
+        }
+
       /* And write it. */
       err = do_export_one_keyblock (ctrl, keyblock, keyid,
                                     out_help? out_help : out,
@@ -1901,52 +1993,22 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
         {
           /* We want to write PKA or DANE records.  OUT_HELP has the
            * keyblock and we print a record for each uid to OUT. */
-          char *hexdata;
           const void *data;
-          void *vp;
           size_t datalen;
-          estream_t fp;
 
           iobuf_flush_temp (out_help);
           data = iobuf_get_temp_buffer (out_help);
           datalen = iobuf_get_temp_length (out_help);
-          hexdata = bin2hex (data, datalen, NULL);
-          if (!hexdata)
-            {
-              err = gpg_error_from_syserror ();
-              goto leave;
-            }
-          iobuf_close (out_help);
-          out_help = iobuf_temp ();
-          ascii_strlwr (hexdata);
-          fp = es_fopenmem (0, "rw,samethread");
-          if (!fp)
-            {
-              err = gpg_error_from_syserror ();
-              xfree (hexdata);
-              goto leave;
-            }
 
-          {
-            char *hexfpr = hexfingerprint (pk, NULL, 0);
-            err = print_pka_or_dane_records (keyblock, hexdata, hexfpr, fp,
-                                             (options & EXPORT_PKA_FORMAT),
-                                             (options & EXPORT_DANE_FORMAT));
-            xfree (hexfpr);
-          }
-          xfree (hexdata);
+          err = print_pka_or_dane_records (out,
+                                           keyblock, pk, data, datalen,
+                                           (options & EXPORT_PKA_FORMAT),
+                                           (options & EXPORT_DANE_FORMAT));
           if (err)
-            {
-              es_fclose (fp);
-              goto leave;
-            }
-          es_fputc (0, fp);
-          if (es_fclose_snatch (fp, &vp, NULL))
-            {
-              err = gpg_error_from_syserror ();
-              goto leave;
-            }
-          iobuf_writestr (out, vp);
+            goto leave;
+
+          iobuf_close (out_help);
+          out_help = iobuf_temp ();
         }
 
     }