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