Since gpgme 1.9.0 it is possible to list keys directly from a file
without importing it into gpg' own keyring.  This patch implements
this in a backward compatible way.

Unfortunately we need to check for a suitable gpgme version at build
time and also for a suitable gpg version at runtime.  This is
implemented by a version check which requires to call or include code
taken from libgpg-error (aka gpgrt).  However this library is anyway a
dependency of gpgme and thus does not pose any extra burden.

The functions parse_version_number, parse_version_string, and
cmp_version_strings are taken from libgpg-error's repo at 2018-11-20.
Libgpg-error is distributed under the LGPL-2.1-or-later.
---
 crypt-gpgme.c | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 238 insertions(+), 14 deletions(-)

diff --git a/crypt-gpgme.c b/crypt-gpgme.c
index ab0e2eec..515d81b3 100644
--- a/crypt-gpgme.c
+++ b/crypt-gpgme.c
@@ -3,7 +3,7 @@
  * Copyright (C) 1998-2000 Thomas Roessler <roess...@does-not-exist.org>
  * Copyright (C) 2001 Thomas Roessler <roess...@does-not-exist.org>
  *                     Oliver Ehli <e...@acm.org>
- * Copyright (C) 2002-2004 g10 Code GmbH
+ * Copyright (C) 2002-2004, 2018 g10 Code GmbH
  * Copyright (C) 2010,2012-2013 Michael R. Elkins <m...@sigpipe.org>
  *
  *     This program is free software; you can redistribute it and/or modify
@@ -139,7 +139,6 @@ digit_or_letter (const unsigned char *s)
            || (*s >= 'a' && *s <= 'z'));
 }
 
-
 /* Print the utf-8 encoded string BUF of length LEN bytes to stream
    FP. Convert the character set. */
 static void
@@ -160,6 +159,182 @@ print_utf8 (FILE *fp, const char *buf, size_t len)
 }
 
 
+/* Compare function for version strings.  The return value is
+ * like strcmp().  LEVEL may be
+ *   0 - reserved
+ *   1 - format is "<major><patchlevel>".
+ *   2 - format is "<major>.<minor><patchlevel>".
+ *   3 - format is "<major>.<minor>.<micro><patchlevel>".
+ * To ignore the patchlevel in the comparison add 10 to LEVEL.  To get
+ * a reverse sorting order use a negative number.   */
+#if GPGRT_VERSION_NUMBER >= 0x012100 /* gpgme >= 1.33 */
+static int
+cmp_version_strings (const char *a, const char *b, int level)
+{
+  return gpgrt_cmp_version (a, b, level);
+}
+#else /* gpgme < 1.33 */
+/* This function parses the first portion of the version number S and
+ * stores it at NUMBER.  On success, this function returns a pointer
+ * into S starting with the first character, which is not part of the
+ * initial number portion; on failure, NULL is returned.  */
+static const char *parse_version_number (const char *s, int *number)
+{
+  int val = 0;
+
+  if (*s == '0' && digitp (s+1))
+    return NULL;  /* Leading zeros are not allowed.  */
+  for (; digitp (s); s++)
+    {
+      val *= 10;
+      val += *s - '0';
+    }
+  *number = val;
+  return val < 0 ? NULL : s;
+}
+/* This function breaks up the complete string-representation of the
+ * version number S, which is of the following struture: <major
+ * number>.<minor number>.<micro number><patch level>.  The major,
+ * minor and micro number components will be stored in *MAJOR, *MINOR
+ * and *MICRO.  If MINOR or MICRO is NULL the version number is
+ * assumed to have just 1 respective 2 parts.
+ *
+ * On success, the last component, the patch level, will be returned;
+ * in failure, NULL will be returned.  */
+static const char *parse_version_string (const char *s, int *major,
+                                         int *minor, int *micro)
+{
+  s = parse_version_number (s, major);
+  if (!s)
+    return NULL;
+  if (!minor)
+    {
+      if (*s == '.')
+        s++;
+    }
+  else
+    {
+      if (*s != '.')
+        return NULL;
+      s++;
+      s = parse_version_number (s, minor);
+      if (!s)
+        return NULL;
+      if (!micro)
+        {
+          if (*s == '.')
+            s++;
+        }
+      else
+        {
+          if (*s != '.')
+            return NULL;
+          s++;
+          s = parse_version_number (s, micro);
+          if (!s)
+            return NULL;
+        }
+    }
+  return s; /* patchlevel */
+}
+/* Substitute for the gpgrt based implementation.
+ * See above for a description.   */
+static int
+cmp_version_strings (const char *a, const char *b, int level)
+{
+  int a_major, a_minor, a_micro;
+  int b_major, b_minor, b_micro;
+  const char *a_plvl, *b_plvl;
+  int r;
+  int ignore_plvl;
+  int positive, negative;
+
+  if (level < 0)
+    {
+      positive = -1;
+      negative = 1;
+      level = 0 - level;
+    }
+  else
+    {
+      positive = 1;
+      negative = -1;
+    }
+  if ((ignore_plvl = (level > 9)))
+    level %= 10;
+
+  a_major = a_minor = a_micro = 0;
+  a_plvl = parse_version_string (a, &a_major,
+                                 level > 1? &a_minor : NULL,
+                                 level > 2? &a_micro : NULL);
+  if (!a_plvl)
+    a_major = a_minor = a_micro = 0; /* Error.  */
+
+  b_major = b_minor = b_micro = 0;
+  b_plvl = parse_version_string (b, &b_major,
+                                 level > 1? &b_minor : NULL,
+                                 level > 2? &b_micro : NULL);
+  if (!b_plvl)
+    b_major = b_minor = b_micro = 0;
+
+  if (!ignore_plvl)
+    {
+      if (!a_plvl && !b_plvl)
+        return negative;  /* Put invalid strings at the end.  */
+      if (a_plvl && !b_plvl)
+        return positive;
+      if (!a_plvl && b_plvl)
+        return negative;
+    }
+
+  if (a_major > b_major)
+    return positive;
+  if (a_major < b_major)
+    return negative;
+
+  if (a_minor > b_minor)
+    return positive;
+  if (a_minor < b_minor)
+    return negative;
+
+  if (a_micro > b_micro)
+    return positive;
+  if (a_micro < b_micro)
+    return negative;
+
+  if (ignore_plvl)
+    return 0;
+
+  for (; *a_plvl && *b_plvl; a_plvl++, b_plvl++)
+    {
+      if (*a_plvl == '.' && *b_plvl == '.')
+        {
+          r = strcmp (a_plvl, b_plvl);
+          if (!r)
+            return 0;
+          else if ( r > 0 )
+            return positive;
+          else
+            return negative;
+        }
+      else if (*a_plvl == '.')
+        return negative; /* B is larger. */
+      else if (*b_plvl == '.')
+        return positive; /* A is larger. */
+      else if (*a_plvl != *b_plvl)
+        break;
+        }
+  if (*a_plvl == *b_plvl)
+    return 0;
+  else if ((*(signed char *)a_plvl - *(signed char *)b_plvl) > 0)
+    return positive;
+  else
+    return negative;
+}
+#endif /* gpgme < 1.33 */
+
+
+
 /*
  * Key management.
  */
@@ -411,6 +586,7 @@ static gpgme_ctx_t create_gpgme_context (int for_smime)
   return ctx;
 }
 
+
 /* Create a new gpgme data object.  This is a wrapper to die on
    error. */
 static gpgme_data_t create_gpgme_data (void)
@@ -429,6 +605,35 @@ static gpgme_data_t create_gpgme_data (void)
   return data;
 }
 
+
+/* Return true if the OpenPGP engine's version is at least VERSION. */
+static int have_gpg_version (const char *version)
+{
+  static char *engine_version;
+
+  if (!engine_version)
+    {
+      gpgme_ctx_t ctx;
+      gpgme_engine_info_t engineinfo;
+
+      ctx = create_gpgme_context (0);
+      engineinfo = gpgme_ctx_get_engine_info (ctx);
+      while (engineinfo && engineinfo->protocol != GPGME_PROTOCOL_OpenPGP)
+        engineinfo = engineinfo->next;
+      if (!engineinfo)
+        {
+          dprint (1, (debugfile, "Error finding GPGME PGP engine\n"));
+          engine_version = safe_strdup ("0.0.0");
+        }
+      else
+        engine_version = safe_strdup (engineinfo->version);
+      gpgme_release (ctx);
+    }
+
+  return cmp_version_strings (engine_version, version, 3) >= 0;
+}
+
+
 /* Create a new GPGME Data object from the mail body A.  With CONVERT
    passed as true, the lines are converted to CR,LF if required.
    Return NULL on error or the gpgme_data_t object on success. */
@@ -2025,8 +2230,10 @@ int smime_gpgme_decrypt_mime (FILE *fpin, FILE **fpout, 
BODY *b, BODY **cur)
 
 static int pgp_gpgme_extract_keys (gpgme_data_t keydata, FILE** fp, int dryrun)
 {
-  /* there's no side-effect free way to view key data in GPGME,
-   * so we import the key into a temporary keyring */
+  /* Before gpgme 1.9.0 and gpg 2.1.14 there was no side-effect free
+   * way to view key data in GPGME, so we import the key into a
+   * temporary keyring if we detect an older system.  */
+  int legacy_api;
   char tmpdir[_POSIX_PATH_MAX];
   char tmpfile[_POSIX_PATH_MAX];
   gpgme_ctx_t tmpctx;
@@ -2042,13 +2249,15 @@ static int pgp_gpgme_extract_keys (gpgme_data_t 
keydata, FILE** fp, int dryrun)
   int rc = -1;
   time_t tt;
 
-#if GPGME_VERSION_NUMBER >= 0x010900 /* 1.9.0 */
-
+#if GPGME_VERSION_NUMBER >= 0x010900 /* gpgme >= 1.9.0 */
+  legacy_api = !have_gpg_version ("2.1.14");
+#else /* gpgme < 1.9.0 */
+  legacy_api = 1;
 #endif
 
   tmpctx = create_gpgme_context (0);
 
-  if (dryrun)
+  if (dryrun && legacy_api)
   {
     snprintf (tmpdir, sizeof(tmpdir), "%s/mutt-gpgme-XXXXXX", Tempdir);
     if (!mkdtemp (tmpdir))
@@ -2075,11 +2284,14 @@ static int pgp_gpgme_extract_keys (gpgme_data_t 
keydata, FILE** fp, int dryrun)
     }
   }
 
-  if ((err = gpgme_op_import (tmpctx, keydata)) != GPG_ERR_NO_ERROR)
-  {
-    dprint (1, (debugfile, "Error importing key\n"));
-    goto err_tmpdir;
-  }
+  if (!dryrun || legacy_api)
+    {
+      if ((err = gpgme_op_import (tmpctx, keydata)) != GPG_ERR_NO_ERROR)
+        {
+          dprint (1, (debugfile, "Error importing key\n"));
+          goto err_tmpdir;
+        }
+    }
 
   mutt_mktemp (tmpfile, sizeof (tmpfile));
   *fp = safe_fopen (tmpfile, "w+");
@@ -2090,7 +2302,14 @@ static int pgp_gpgme_extract_keys (gpgme_data_t keydata, 
FILE** fp, int dryrun)
   }
   unlink (tmpfile);
 
-  err = gpgme_op_keylist_start (tmpctx, NULL, 0);
+#if GPGME_VERSION_NUMBER >= 0x010900 /* 1.9.0 */
+  if (dryrun && !legacy_api)
+    err = gpgme_op_keylist_from_data_start (tmpctx, keydata, 0);
+  else
+#endif /* gpgme >= 1.9.0 */
+    {
+      err = gpgme_op_keylist_start (tmpctx, NULL, 0);
+    }
   while (!err)
   {
     if ((err = gpgme_op_keylist_next (tmpctx, &key)))
@@ -2132,7 +2351,7 @@ err_fp:
   if (rc)
     safe_fclose (fp);
 err_tmpdir:
-  if (dryrun)
+  if (dryrun && legacy_api)
     mutt_rmtree (tmpdir);
 err_ctx:
   gpgme_release (tmpctx);
@@ -2270,6 +2489,11 @@ void pgp_gpgme_invoke_import (const char *fname)
     mutt_error (_("Error extracting key data!\n"));
     mutt_sleep (1);
   }
+  else
+  {
+    fseek (out, 0, SEEK_SET);
+    mutt_copy_stream (out, stdout);
+  }
   gpgme_data_release (keydata);
   safe_fclose (&in);
   safe_fclose (&out);
-- 
2.11.0


Reply via email to