summaryrefslogtreecommitdiffstats
path: root/crypt-gpgme.c
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2018-12-03 08:41:55 +0100
committerKevin McCarthy <kevin@8t8.us>2018-12-04 19:46:17 -0800
commit55c18b7e2fc2b7779beb6cb69d9524a402e9f2ef (patch)
treeac832d238a0697d1e2dcc5e89853aadbeceb5aeb /crypt-gpgme.c
parentbf4a65ee6ea0bfedea8294334cc8fa5a2d51bd92 (diff)
Try to avoid creation of temp. directory for key import.
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.
Diffstat (limited to 'crypt-gpgme.c')
-rw-r--r--crypt-gpgme.c252
1 files 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 <roessler@does-not-exist.org>
* Copyright (C) 2001 Thomas Roessler <roessler@does-not-exist.org>
* Oliver Ehli <elmy@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 <me@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);