From c9530c4773a9d0b50c589098a03a47bcdfc75ea0 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 15:36:26 +0100 Subject: gpgv: Generalize apt_error to apt_msg(), add apt_warning() This allows printing warnings and errors, --- apt-pkg/contrib/gpgv.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index 147f15d9d..aab7851a1 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -112,7 +112,9 @@ static bool operator!=(LineBuffer const &buf, std::string_view const exp) noexce And as a cherry on the cake, we use our apt-key wrapper to do part of the lifting in regards to merging keyrings. Fun for the whole family. */ -static void APT_PRINTF(4) apt_error(std::ostream &outterm, int const statusfd, int fd[2], const char *format, ...) +#define apt_error(...) apt_msg("ERROR", __VA_ARGS__) +#define apt_warning(...) apt_msg("WARNING", __VA_ARGS__) +static void APT_PRINTF(5) apt_msg(std::string const &tag, std::ostream &outterm, int const statusfd, int fd[2], const char *format, ...) { std::ostringstream outstr; std::ostream &out = (statusfd == -1) ? outterm : outstr; @@ -128,10 +130,10 @@ static void APT_PRINTF(4) apt_error(std::ostream &outterm, int const statusfd, i } if (statusfd != -1) { - auto const errtag = "[APTKEY:] ERROR "; + auto const errtag = "[APTKEY:] " + tag + " "; outstr << '\n'; auto const errtext = outstr.str(); - if (not FileFd::Write(fd[1], errtag, strlen(errtag)) || + if (not FileFd::Write(fd[1], errtag.data(), errtag.size()) || not FileFd::Write(fd[1], errtext.data(), errtext.size())) outterm << errtext << std::flush; } -- cgit v1.2.3-70-g09d2 From 0e69c1fa5b8a4a1237933fa47113ea839a66f8a0 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 17:45:11 +0100 Subject: Do not implode key file name vector for calling gpgv Converting it to a comma-separated string and back loses precision. --- apt-pkg/contrib/gpgv.cc | 10 ++++++++-- apt-pkg/contrib/gpgv.h | 2 ++ methods/gpgv.cc | 3 +-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index aab7851a1..6def6d625 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -140,6 +140,13 @@ static void APT_PRINTF(5) apt_msg(std::string const &tag, std::ostream &outterm, } void ExecGPGV(std::string const &File, std::string const &FileGPG, int const &statusfd, int fd[2], std::string const &key) +{ + auto const keyFiles = VectorizeString(key, ','); + ExecGPGV(File, FileGPG, statusfd, fd, keyFiles); +} + +void ExecGPGV(std::string const &File, std::string const &FileGPG, + int const &statusfd, int fd[2], std::vector const &KeyFiles) { #define EINTERNAL 111 std::string const aptkey = _config->Find("Dir::Bin::apt-key", CMAKE_INSTALL_FULL_BINDIR "/apt-key"); @@ -160,8 +167,7 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, Args.push_back(aptkey.c_str()); Args.push_back("--quiet"); Args.push_back("--readonly"); - auto const keysFileFpr = VectorizeString(key, ','); - for (auto const &k: keysFileFpr) + for (auto const &k : KeyFiles) { if (unlikely(k.empty())) continue; diff --git a/apt-pkg/contrib/gpgv.h b/apt-pkg/contrib/gpgv.h index 1f3ef26f9..62c92f271 100644 --- a/apt-pkg/contrib/gpgv.h +++ b/apt-pkg/contrib/gpgv.h @@ -39,6 +39,8 @@ class FileFd; * @param fd is used as a pipe for the standard output of gpgv * @param key is the specific one to be used instead of using all */ +APT_PUBLIC void ExecGPGV(std::string const &File, std::string const &FileSig, + int const &statusfd, int fd[2], std::vector const &KeyFiles) APT_NORETURN; APT_PUBLIC void ExecGPGV(std::string const &File, std::string const &FileSig, int const &statusfd, int fd[2], std::string const &Key = "") APT_NORETURN; inline APT_NORETURN void ExecGPGV(std::string const &File, std::string const &FileSig, diff --git a/methods/gpgv.cc b/methods/gpgv.cc index 4a0866555..9f2ccb174 100644 --- a/methods/gpgv.cc +++ b/methods/gpgv.cc @@ -201,9 +201,8 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, else if (pid == 0) { std::ostringstream keys; - implodeVector(keyFiles, keys, ","); setenv("APT_KEY_NO_LEGACY_KEYRING", "1", true); - ExecGPGV(outfile, file, 3, fd, keys.str()); + ExecGPGV(outfile, file, 3, fd, keyFiles); } close(fd[1]); -- cgit v1.2.3-70-g09d2 From da306ae6cfa9170170775eeecf45db7ba527ba23 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 18:11:44 +0100 Subject: apt-key: Only cat supported keyrings into the merged one --- cmdline/apt-key.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmdline/apt-key.in b/cmdline/apt-key.in index 99a31bdd1..763fc7baf 100644 --- a/cmdline/apt-key.in +++ b/cmdline/apt-key.in @@ -470,7 +470,9 @@ dearmor_filename() { fi } catfile() { - cat "$(dearmor_filename "$1")" >> "$2" + if accessible_file_exists "$1" && is_supported_keyring "$1"; then + cat "$(dearmor_filename "$1")" >> "$2" + fi } merge_all_trusted_keyrings_into_pubring() { -- cgit v1.2.3-70-g09d2 From e285d9f75546bda442ace3cba7c6c357623937e3 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 18:32:21 +0100 Subject: apt-key: Temporarily accept 'pub' as an extension for binary gpg keys --- cmdline/apt-key.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmdline/apt-key.in b/cmdline/apt-key.in index 763fc7baf..dd1c52ab0 100644 --- a/cmdline/apt-key.in +++ b/cmdline/apt-key.in @@ -312,7 +312,7 @@ is_supported_keyring() { return 0 fi local FILEEXT="${1##*.}" - if [ "$FILEEXT" = 'gpg' ]; then + if [ "$FILEEXT" = 'gpg' -o "$FILEEXT" = 'pub' ]; then # 0x98, 0x99 and 0xC6 via octal as hex isn't supported by dashs printf if printf '\231' | cmp -s -n 1 - "$1"; then true -- cgit v1.2.3-70-g09d2 From d8d3e51ff18ab75243931c27920191685c19b981 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 16:50:25 +0100 Subject: gpgv: Explictly pass all the keyrings to gpgv from apt --- apt-pkg/contrib/gpgv.cc | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index 6def6d625..8919bc790 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -167,14 +167,30 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, Args.push_back(aptkey.c_str()); Args.push_back("--quiet"); Args.push_back("--readonly"); + auto maybeAddKeyring = [&](std::string const &k) + { + if (struct stat st; stat(k.c_str(), &st) != 0 || st.st_size == 0) + return; + if (access(k.c_str(), R_OK) != 0) + { + apt_warning(std::cerr, statusfd, fd, "The key(s) in the keyring %s are ignored as the file is not readable by user executing apt-key.\n", k.c_str()); + return; + } + Args.push_back("--keyring"); + Args.push_back(k.c_str()); + return; + }; + for (auto const &k : KeyFiles) { if (unlikely(k.empty())) continue; if (k[0] == '/') { - Args.push_back("--keyring"); - Args.push_back(k.c_str()); + if (Debug) + std::clog << "Trying Signed-By: " << k << std::endl; + + maybeAddKeyring(k); } else { @@ -182,8 +198,29 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, Args.push_back(k.c_str()); } } - Args.push_back("verify"); + std::vector Parts; + if (std::find(Args.begin(), Args.end(), "--keyring") == Args.end()) + { + Parts = GetListOfFilesInDir(_config->FindDir("Dir::Etc::TrustedParts"), std::vector{"gpg", "asc"}, true); + if (char *env = getenv("APT_KEY_NO_LEGACY_KEYRING"); env == nullptr || not StringToBool(env, false)) + Parts.insert(Parts.begin(), _config->FindFile("Dir::Etc::Trusted")); + for (auto &Part : Parts) + { + if (Debug) + std::clog << "Trying TrustedPart: " << Part << std::endl; + maybeAddKeyring(Part); + } + } + + // If we do not give it any keyring, gpgv shouts keydb errors at us + if (std::find(Args.begin(), Args.end(), "--keyring") == Args.end()) + { + Args.push_back("--keyring"); + Args.push_back("/dev/null"); + } + + Args.push_back("verify"); char statusfdstr[10]; if (statusfd != -1) { -- cgit v1.2.3-70-g09d2 From 0211fd6451bd67e5c590ac89c8696310ac8dd78c Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 19:13:07 +0100 Subject: gpgv: Use std::string instead of const char * This is a bit more overhead here, but if we need to build temporary strings this is a lot cleaner. --- apt-pkg/contrib/gpgv.cc | 72 +++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index 8919bc790..dd1a279fc 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -153,18 +153,19 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, bool const Debug = _config->FindB("Debug::Acquire::gpgv", false); struct exiter { - std::vector files; + std::vector files; void operator ()(int code) APT_NORETURN { - std::for_each(files.begin(), files.end(), unlink); + std::for_each(files.begin(), files.end(), [](auto f) + { unlink(f.c_str()); }); exit(code); } } local_exit; - std::vector Args; + std::vector Args; Args.reserve(10); - Args.push_back(aptkey.c_str()); + Args.push_back(aptkey); Args.push_back("--quiet"); Args.push_back("--readonly"); auto maybeAddKeyring = [&](std::string const &k) @@ -177,7 +178,7 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, return; } Args.push_back("--keyring"); - Args.push_back(k.c_str()); + Args.push_back(k); return; }; @@ -195,7 +196,7 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, else { Args.push_back("--keyid"); - Args.push_back(k.c_str()); + Args.push_back(k); } } @@ -238,38 +239,28 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, { if (Opts->Value.empty()) continue; - Args.push_back(Opts->Value.c_str()); + Args.push_back(Opts->Value); } } enum { DETACHED, CLEARSIGNED } releaseSignature = (FileGPG != File) ? DETACHED : CLEARSIGNED; - std::unique_ptr sig; - std::unique_ptr data; - std::unique_ptr conf; // Dump the configuration so apt-key picks up the correct Dir values { - { - std::string tmpfile; - strprintf(tmpfile, "%s/apt.conf.XXXXXX", GetTempDir().c_str()); - conf.reset(strdup(tmpfile.c_str())); - } - if (conf == nullptr) { - apt_error(std::cerr, statusfd, fd, "Couldn't create tempfile names for passing config to apt-key"); - local_exit(EINTERNAL); - } - int confFd = mkstemp(conf.get()); + std::string tmpfile; + strprintf(tmpfile, "%s/apt.conf.XXXXXX", GetTempDir().c_str()); + int confFd = mkstemp(tmpfile.data()); if (confFd == -1) { - apt_error(std::cerr, statusfd, fd, "Couldn't create temporary file %s for passing config to apt-key", conf.get()); + apt_error(std::cerr, statusfd, fd, "Couldn't create temporary file %s for passing config to apt-key", tmpfile.c_str()); local_exit(EINTERNAL); } - local_exit.files.push_back(conf.get()); + local_exit.files.push_back(tmpfile); - std::ofstream confStream(conf.get()); + std::ofstream confStream(tmpfile); close(confFd); _config->Dump(confStream); confStream.close(); - setenv("APT_CONFIG", conf.get(), 1); + setenv("APT_CONFIG", tmpfile.c_str(), 1); } // Tell apt-key not to emit warnings @@ -352,21 +343,19 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, local_exit(112); } - Args.push_back(FileGPG.c_str()); - Args.push_back(File.c_str()); + Args.push_back(FileGPG); + Args.push_back(File); } else // clear-signed file { FileFd signature; if (GetTempFile("apt.sig", false, &signature) == nullptr) local_exit(EINTERNAL); - sig.reset(strdup(signature.Name().c_str())); - local_exit.files.push_back(sig.get()); + local_exit.files.push_back(signature.Name()); FileFd message; if (GetTempFile("apt.data", false, &message) == nullptr) local_exit(EINTERNAL); - data.reset(strdup(message.Name().c_str())); - local_exit.files.push_back(data.get()); + local_exit.files.push_back(message.Name()); if (signature.Failed() || message.Failed() || not SplitClearSignedFile(File, &message, nullptr, &signature)) @@ -374,17 +363,15 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, apt_error(std::cerr, statusfd, fd, "Splitting up %s into data and signature failed", File.c_str()); local_exit(112); } - Args.push_back(sig.get()); - Args.push_back(data.get()); + Args.push_back(signature.Name()); + Args.push_back(message.Name()); } - Args.push_back(NULL); - if (Debug) { std::clog << "Preparing to exec: "; - for (std::vector::const_iterator a = Args.begin(); *a != NULL; ++a) - std::clog << " " << *a; + for (auto const &a : Args) + std::clog << " " << a; std::clog << std::endl; } @@ -405,20 +392,27 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, putenv((char *)"LC_MESSAGES="); } + // Translate the argument list to a C array. This should happen before + // the fork so we don't allocate money between fork() and execvp(). + std::vector cArgs; + cArgs.reserve(Args.size() + 1); + for (auto const &arg : Args) + cArgs.push_back(arg.c_str()); + cArgs.push_back(nullptr); // We have created tempfiles we have to clean up // and we do an additional check, so fork yet another time … pid_t pid = ExecFork(); if(pid < 0) { - apt_error(std::cerr, statusfd, fd, "Fork failed for %s to check %s", Args[0], File.c_str()); + apt_error(std::cerr, statusfd, fd, "Fork failed for %s to check %s", Args[0].c_str(), File.c_str()); local_exit(EINTERNAL); } if(pid == 0) { if (statusfd != -1) dup2(fd[1], statusfd); - execvp(Args[0], (char **) &Args[0]); - apt_error(std::cerr, statusfd, fd, "Couldn't execute %s to check %s", Args[0], File.c_str()); + execvp(cArgs[0], (char **) &cArgs[0]); + apt_error(std::cerr, statusfd, fd, "Couldn't execute %s to check %s", Args[0].c_str(), File.c_str()); local_exit(EINTERNAL); } -- cgit v1.2.3-70-g09d2 From 62cc071f88cb33c1f6213c9dbd54a10135c5ad34 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 19:32:06 +0100 Subject: strutl: Add Base64Decode Lifted from the dpkg mtree code that mjg59 submitted in https://lists.debian.org/debian-dpkg/2018/05/msg00005.html Which as dpkg is GPL-2.0+ also is GPL-2.0+ --- apt-pkg/contrib/strutl.cc | 67 +++++++++++++++++++++++++++++++++++++++++++++ apt-pkg/contrib/strutl.h | 1 + test/libapt/strutil_test.cc | 21 +++++++++++++- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/apt-pkg/contrib/strutl.cc b/apt-pkg/contrib/strutl.cc index ec61c7e2b..69a67188e 100644 --- a/apt-pkg/contrib/strutl.cc +++ b/apt-pkg/contrib/strutl.cc @@ -624,6 +624,73 @@ string Base64Encode(const string &S) return Final; } + /*}}}*/ +// Base64Decode - Base64 decoding routine for short strings /*{{{*/ +// --------------------------------------------------------------------- +// Taken from mjg59's dpkg code: https://lists.debian.org/debian-dpkg/2018/05/msg00002.html +std::string Base64Decode(const std::string_view in) +{ + constexpr std::array mime_base64_rank{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; + std::string out; + unsigned char rank; + unsigned char last[2]; + unsigned int v; + int i; + + /* convert 4 base64 bytes to 3 normal bytes */ + v = 0; + i = 0; + + last[0] = last[1] = 0; + + /* we use the sign in the state to determine if we got a padding character + in the previous sequence */ + if (i < 0) + { + i = -i; + last[0] = '='; + } + + for (const unsigned char c : in) + { + rank = mime_base64_rank[c]; + if (rank != 0xff) + { + last[1] = last[0]; + last[0] = c; + v = (v << 6) | rank; + i++; + if (i == 4) + { + out += v >> 16; + if (last[1] != '=') + out += v >> 8; + if (last[0] != '=') + out += v; + i = 0; + } + } + } + + return out; +} + /*}}}*/ // stringcmp - Arbitrary string compare /*{{{*/ // --------------------------------------------------------------------- diff --git a/apt-pkg/contrib/strutl.h b/apt-pkg/contrib/strutl.h index 90d9674fd..91f5fe44b 100644 --- a/apt-pkg/contrib/strutl.h +++ b/apt-pkg/contrib/strutl.h @@ -69,6 +69,7 @@ APT_PUBLIC std::string DeEscapeString(const std::string &input); APT_PUBLIC std::string SizeToStr(double Bytes); APT_PUBLIC std::string TimeToStr(unsigned long Sec); APT_PUBLIC std::string Base64Encode(const std::string &Str); +APT_PUBLIC std::string Base64Decode(const std::string_view in); APT_PUBLIC std::string OutputInDepth(const unsigned long Depth, const char* Separator=" "); APT_PUBLIC std::string URItoFileName(const std::string &URI); /** returns a datetime string as needed by HTTP/1.1 and Debian files. diff --git a/test/libapt/strutil_test.cc b/test/libapt/strutil_test.cc index 500ba98da..9320f8fa9 100644 --- a/test/libapt/strutil_test.cc +++ b/test/libapt/strutil_test.cc @@ -179,6 +179,24 @@ TEST(StrUtilTest,Base64Encode) EXPECT_EQ("/A==", Base64Encode("\xfc")); EXPECT_EQ("//8=", Base64Encode("\xff\xff")); } +TEST(StrUtilTest,Base64Decode) +{ + EXPECT_EQ(Base64Decode("QWxhZGRpbjpvcGVuIHNlc2FtZQ=="), "Aladdin:open sesame"); + EXPECT_EQ(Base64Decode("cGxlYXN1cmUu"), "pleasure."); + EXPECT_EQ(Base64Decode("bGVhc3VyZS4="), "leasure."); + EXPECT_EQ(Base64Decode("ZWFzdXJlLg=="), "easure."); + EXPECT_EQ(Base64Decode("YXN1cmUu"), "asure."); + EXPECT_EQ(Base64Decode("c3VyZS4="), "sure."); + EXPECT_EQ(Base64Decode("dXJlLg=="), "ure."); + EXPECT_EQ(Base64Decode("cmUu"), "re."); + EXPECT_EQ(Base64Decode("ZS4="), "e."); + EXPECT_EQ(Base64Decode("Lg=="), "."); + EXPECT_EQ(Base64Decode(""), ""); + EXPECT_EQ(Base64Decode("IA=="), "\x20"); + EXPECT_EQ(Base64Decode("/w=="), "\xff"); + EXPECT_EQ(Base64Decode("/A=="), "\xfc"); + EXPECT_EQ(Base64Decode("//8="), "\xff\xff"); +} static void ReadMessagesTestWithNewLine(char const * const nl, char const * const ab) { SCOPED_TRACE(SubstVar(SubstVar(nl, "\n", "n"), "\r", "r") + " # " + ab); @@ -318,7 +336,8 @@ TEST(StrUtilTest,RFC1123StrToTime) time_t t; EXPECT_TRUE(RFC1123StrToTime("Sun, 06 Nov 1994 08:49:37 GMT", t)); EXPECT_EQ(784111777, t); - } { + } + { time_t t; EXPECT_TRUE(RFC1123StrToTime("Sun, 6 Nov 1994 08:49:37 UTC", t)); EXPECT_EQ(784111777, t); -- cgit v1.2.3-70-g09d2 From 623bc8a69d0e89c0f18305403741af4fda8d87bf Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 19:41:45 +0100 Subject: gpgv: Verify keyrings and dearmor outside apt-key --- apt-pkg/contrib/gpgv.cc | 59 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index dd1a279fc..fdc292f4f 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -168,17 +168,66 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, Args.push_back(aptkey); Args.push_back("--quiet"); Args.push_back("--readonly"); + auto dearmorKeyOrCheckFormat = [&](std::string const &k) -> std::string + { + FileFd keyFd(k, FileFd::ReadOnly); + if (not keyFd.IsOpen()) + { + apt_warning(std::cerr, statusfd, fd, "The key(s) in the keyring %s are ignored as the file is not readable by user executing apt-key.\n", k.c_str()); + return ""; + } + if (APT::String::Endswith(k, ".gpg") || APT::String::Endswith(k, ".pub")) + { + unsigned char c; + if (not keyFd.Read(&c, sizeof(c))) + goto err; + // Identify the leading byte of an OpenPGP public key packet + // 0x98 -- old-format OpenPGP public key packet, up to 255 octets + // 0x99 -- old-format OpenPGP public key packet, 256-65535 octets + // 0xc6 -- new-format OpenPGP public key packet, any length + if (c == 0x98 || c == 0x99 || c == 0xc6) + return k; + } + else if (APT::String::Endswith(k, ".asc")) + { + std::string b64msg; + int state = 0; + for (std::string line; keyFd.ReadLine(line);) + { + line = APT::String::Strip(line); + if (APT::String::Startswith(line, "-----BEGIN PGP PUBLIC KEY BLOCK-----")) + state = 1; + else if (state == 1 && line == "") + state = 2; + else if (state == 2 && line != "" && line[0] != '=' && line[0] != '-') + b64msg += line; + else if (APT::String::Startswith(line, "-----END")) + state = 3; + } + if (state != 3) + goto err; + + FileFd dearmoredFd; + if (GetTempFile("apt.XXXXXX.gpg", false, &dearmoredFd) == nullptr) + local_exit(EINTERNAL); + if (auto decoded = Base64Decode(b64msg); not decoded.empty()) + dearmoredFd.Write(decoded.data(), decoded.size()); + local_exit.files.push_back(dearmoredFd.Name()); + return dearmoredFd.Name(); + } + err: + apt_warning(std::cerr, statusfd, fd, "The key(s) in the keyring %s are ignored as the file has an unsupported filetype.", k.c_str()); + return ""; + }; auto maybeAddKeyring = [&](std::string const &k) { if (struct stat st; stat(k.c_str(), &st) != 0 || st.st_size == 0) return; - if (access(k.c_str(), R_OK) != 0) + if (auto cleanKey = dearmorKeyOrCheckFormat(k); not cleanKey.empty()) { - apt_warning(std::cerr, statusfd, fd, "The key(s) in the keyring %s are ignored as the file is not readable by user executing apt-key.\n", k.c_str()); - return; + Args.push_back("--keyring"); + Args.push_back(cleanKey); } - Args.push_back("--keyring"); - Args.push_back(k); return; }; -- cgit v1.2.3-70-g09d2 From 325b0c89358dae042ebfe0ba6dcbcc43253927a6 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 19:59:24 +0100 Subject: Directly call gpgv instead of apt-key --- apt-pkg/contrib/gpgv.cc | 55 ++++++++++++++++++++------------------- test/integration/framework | 9 +------ test/integration/test-method-gpgv | 22 ++++++++-------- 3 files changed, 40 insertions(+), 46 deletions(-) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index fdc292f4f..dd4d5cfb6 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -149,8 +149,12 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, int const &statusfd, int fd[2], std::vector const &KeyFiles) { #define EINTERNAL 111 - std::string const aptkey = _config->Find("Dir::Bin::apt-key", CMAKE_INSTALL_FULL_BINDIR "/apt-key"); - + const std::string gpgvVariants[] = { + "/usr/bin/gpgv-sq", + "/usr/bin/gpgv", + "/usr/bin/gpgv2", + "/usr/bin/gpgv", + }; bool const Debug = _config->FindB("Debug::Acquire::gpgv", false); struct exiter { std::vector files; @@ -161,13 +165,32 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, } } local_exit; + std::string gpgv = _config->Find("Apt::Key::gpgvcommand"); + if (gpgv.empty() || !FileExists(gpgv)) + { + gpgv = ""; + for (auto gpgvVariant : gpgvVariants) + { + if (FileExists(gpgvVariant)) + { + gpgv = gpgvVariant; + break; + } + } + + if (gpgv.empty()) + { + apt_error(std::cerr, statusfd, fd, "Couldn't find a gpgv binary"); + local_exit(EINTERNAL); + } + } std::vector Args; Args.reserve(10); - Args.push_back(aptkey); - Args.push_back("--quiet"); - Args.push_back("--readonly"); + Args.push_back(gpgv); + Args.push_back("--ignore-time-conflict"); + auto dearmorKeyOrCheckFormat = [&](std::string const &k) -> std::string { FileFd keyFd(k, FileFd::ReadOnly); @@ -270,7 +293,6 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, Args.push_back("/dev/null"); } - Args.push_back("verify"); char statusfdstr[10]; if (statusfd != -1) { @@ -294,27 +316,6 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, enum { DETACHED, CLEARSIGNED } releaseSignature = (FileGPG != File) ? DETACHED : CLEARSIGNED; - // Dump the configuration so apt-key picks up the correct Dir values - { - std::string tmpfile; - strprintf(tmpfile, "%s/apt.conf.XXXXXX", GetTempDir().c_str()); - int confFd = mkstemp(tmpfile.data()); - if (confFd == -1) { - apt_error(std::cerr, statusfd, fd, "Couldn't create temporary file %s for passing config to apt-key", tmpfile.c_str()); - local_exit(EINTERNAL); - } - local_exit.files.push_back(tmpfile); - - std::ofstream confStream(tmpfile); - close(confFd); - _config->Dump(confStream); - confStream.close(); - setenv("APT_CONFIG", tmpfile.c_str(), 1); - } - - // Tell apt-key not to emit warnings - setenv("APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE", "1", 1); - if (releaseSignature == DETACHED) { auto detached = make_unique_FILE(FileGPG, "r"); diff --git a/test/integration/framework b/test/integration/framework index ad87d17c7..a64488f0e 100644 --- a/test/integration/framework +++ b/test/integration/framework @@ -457,15 +457,8 @@ _setupprojectenvironment() { echo "APT::Get::Show-User-Simulation-Note \"false\";" >> aptconfig.conf echo "Binary::apt::APT::Output-Version \"0\";" >> aptconfig.conf echo "Dir::Bin::Methods \"${TMPWORKINGDIRECTORY}/rootdir/usr/lib/apt/methods\";" >> aptconfig.conf - # either store apt-key were we can access it, even if we run it as a different user - #cp "${APTCMDLINEBINDIR}/apt-key" "${TMPWORKINGDIRECTORY}/rootdir/usr/bin/" - #chmod o+rx "${TMPWORKINGDIRECTORY}/rootdir/usr/bin/apt-key" - #echo "Dir::Bin::apt-key \"${TMPWORKINGDIRECTORY}/rootdir/usr/bin/apt-key\";" >> aptconfig.conf - # destroys coverage reporting though, so we disable changing user for the calling gpgv - echo "Dir::Bin::apt-key \"${APTCMDLINEBINDIR}/apt-key\";" >> aptconfig.conf if [ "$(id -u)" = '0' ]; then - echo 'Binary::gpgv::APT::Sandbox::User "root";' >> aptconfig.conf - # same for the solver executables + # run the solvers as root so we don't lose coverage data echo 'APT::Solver::RunAsUser "root";' >> aptconfig.conf echo 'APT::Planner::RunAsUser "root";' >> aptconfig.conf fi diff --git a/test/integration/test-method-gpgv b/test/integration/test-method-gpgv index ffaa72c8f..5ce685c8b 100755 --- a/test/integration/test-method-gpgv +++ b/test/integration/test-method-gpgv @@ -7,7 +7,7 @@ TESTDIR="$(readlink -f "$(dirname "$0")")" setupenvironment configarchitecture 'i386' -cat > faked-apt-key < faked-gpgv <&\${GPGSTATUSFD} gpgv.output cat gpgv.output EOF -chmod +x faked-apt-key +chmod +x faked-gpgv testgpgv() { echo "$4" > gpgv.output @@ -114,7 +114,7 @@ EOF gpgvmethod() { echo "601 Configuration Config-Item: Debug::Acquire::gpgv=1 -Config-Item: Dir::Bin::apt-key=./faked-apt-key +Config-Item: APT::Key::GPGVCommand=$PWD/faked-gpgv Config-Item: APT::Hashes::SHA1::Weak=true Config-Item: APT::Key::Assert-Pubkey-Algo=>=rsa2048,nistp256,brainpoolP256r1 Config-Item: APT::Key::Assert-Pubkey-Algo::Next=>=rsa2048,nistp256 @@ -130,7 +130,7 @@ testrun gpgvmethod() { echo "601 Configuration Config-Item: Debug::Acquire::gpgv=1 -Config-Item: Dir::Bin::apt-key=./faked-apt-key +Config-Item: APT::Key::GPGVCommand=$PWD/faked-gpgv Config-Item: APT::Hashes::SHA1::Weak=true Config-Item: APT::Key::Assert-Pubkey-Algo=>=rsa2048,nistp256,brainpoolP256r1 Config-Item: APT::Key::Assert-Pubkey-Algo::Next=>=rsa2048,nistp256 @@ -147,7 +147,7 @@ testrun gpgvmethod() { echo "601 Configuration Config-Item: Debug::Acquire::gpgv=1 -Config-Item: Dir::Bin::apt-key=./faked-apt-key +Config-Item: APT::Key::GPGVCommand=$PWD/faked-gpgv Config-Item: APT::Hashes::SHA1::Weak=true Config-Item: APT::Key::Assert-Pubkey-Algo=>=rsa2048,nistp256,brainpoolP256r1 Config-Item: APT::Key::Assert-Pubkey-Algo::Next=>=rsa2048,nistp256 @@ -173,7 +173,7 @@ testsuccess grep 'verified because the public key is not available: GOODSIG' met gpgvmethod() { echo "601 Configuration Config-Item: Debug::Acquire::gpgv=1 -Config-Item: Dir::Bin::apt-key=./faked-apt-key +Config-Item: APT::Key::GPGVCommand=$PWD/faked-gpgv Config-Item: APT::Hashes::SHA1::Weak=true Config-Item: APT::Key::Assert-Pubkey-Algo=>=rsa2048,nistp256,brainpoolP256r1 Config-Item: APT::Key::Assert-Pubkey-Algo::Next=>=rsa2048,nistp256 @@ -204,26 +204,26 @@ setupaptarchive --no-update echo '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Sebastian Subkey [GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2018-08-16 1534459673 0 4 0 1 11 00 4281DEDBD466EAE8C1F4157E5B6896415D44C43E' > gpgv.output -testsuccess apt update -o Dir::Bin::apt-key="./faked-apt-key" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 +testsuccess apt update -o APT::Key::GPGVCommand="$PWD/faked-gpgv" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 rm -rf rootdir/var/lib/apt/lists echo '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Sebastian Subkey ' > gpgv.output -testfailure apt update -o Dir::Bin::apt-key="./faked-apt-key" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 +testfailure apt update -o APT::Key::GPGVCommand="$PWD/faked-gpgv" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 rm -rf rootdir/var/lib/apt/lists echo '[GNUPG:] VALIDSIG 34A8E9D18DB320F367E8EAA05A90D141DBAC8DAE 2018-08-16 1534459673 0 4 0 1 11 00 4281DEDBD466EAE8C1F4157E5B6896415D44C43E' > gpgv.output -testfailure apt update -o Dir::Bin::apt-key="./faked-apt-key" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 +testfailure apt update -o APT::Key::GPGVCommand="$PWD/faked-gpgv" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 rm -rf rootdir/var/lib/apt/lists echo '[GNUPG:] GOODSIG 5A90D141DBAC8DAE Sebastian Subkey [GNUPG:] VALIDSIG 0000000000000000000000000000000000000000 2018-08-16 1534459673 0 4 0 1 11 00 4281DEDBD466EAE8C1F4157E5B6896415D44C43E' > gpgv.output -testfailure apt update -o Dir::Bin::apt-key="./faked-apt-key" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 +testfailure apt update -o APT::Key::GPGVCommand="$PWD/faked-gpgv" -o Debug::pkgAcquire::Worker=1 -o Debug::Acquire::gpgv=1 rm -rf rootdir/var/lib/apt/lists gpgvmethod() { echo "601 Configuration Config-Item: Debug::Acquire::gpgv=1 -Config-Item: Dir::Bin::apt-key=./faked-apt-key +Config-Item: APT::Key::GPGVCommand=$PWD/faked-gpgv Config-Item: APT::Hashes::SHA1::Weak=true Config-Item: APT::Key::Assert-Pubkey-Algo::Next=>=invalid -- cgit v1.2.3-70-g09d2 From 0496bc63a3ad1dcbc41d20225c6a0d50fbcf101d Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Sat, 23 Nov 2024 19:50:44 +0100 Subject: gpgv: Add direct support for --assert-pubkey-algo --- apt-pkg/contrib/gpgv.cc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index dd4d5cfb6..611f4bfa7 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -301,6 +301,29 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, Args.push_back(statusfdstr); } + if (auto assertPubkeyAlgo = _config->Find("Apt::Key::assert-pubkey-algo"); not assertPubkeyAlgo.empty()) + { + FileFd dumpOptions; + pid_t child; + if (Debug) + std::clog << "Calling " << gpgv << " --dump-options" << std::endl; + const char* argv[] = {gpgv.c_str(), "--dump-options", nullptr}; + if (not Popen(argv, dumpOptions, child, FileFd::ReadOnly) && Debug) + std::clog << "Failed to call " << gpgv << " --dump-options" << std::endl; + for (std::string line; dumpOptions.ReadLine(line); ) + { + if (Debug) + std::clog << "Read line: " << line << std::endl; + if (APT::String::Strip(line) == "--assert-pubkey-algo") + { + Args.push_back("--assert-pubkey-algo=" + assertPubkeyAlgo); + break; + } + } + dumpOptions.Close(); + waitpid(child, NULL, 0); + } + Configuration::Item const *Opts; Opts = _config->Tree("Acquire::gpgv::Options"); if (Opts != 0) -- cgit v1.2.3-70-g09d2 From 7038347919d59b2f96a8f51f994889a5b4d66cba Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 28 Nov 2024 09:49:58 +0100 Subject: fileutl: Use execvp() in Popen() instead of execv() --- apt-pkg/contrib/fileutl.cc | 2 +- test/libapt/fileutl_test.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apt-pkg/contrib/fileutl.cc b/apt-pkg/contrib/fileutl.cc index a45069ee9..67c5acee2 100644 --- a/apt-pkg/contrib/fileutl.cc +++ b/apt-pkg/contrib/fileutl.cc @@ -3232,7 +3232,7 @@ bool Popen(const char *Args[], FileFd &Fd, pid_t &Child, FileFd::OpenMode Mode, } else if(Mode == FileFd::WriteOnly) dup2(fd, 0); - execv(Args[0], (char**)Args); + execvp(Args[0], (char **)Args); _exit(100); } if(Mode == FileFd::ReadOnly) diff --git a/test/libapt/fileutl_test.cc b/test/libapt/fileutl_test.cc index b35a2d45e..2badf5465 100644 --- a/test/libapt/fileutl_test.cc +++ b/test/libapt/fileutl_test.cc @@ -288,7 +288,7 @@ TEST(FileUtlTest, Popen) OpenFds = Glob("/proc/self/fd/*"); // output something - const char* Args[10] = {"/bin/echo", "meepmeep", NULL}; + const char* Args[10] = {"echo", "meepmeep", NULL}; EXPECT_TRUE(Popen(Args, Fd, Child, FileFd::ReadOnly)); EXPECT_TRUE(Fd.Read(buf, sizeof(buf)-1, &n)); buf[n] = 0; -- cgit v1.2.3-70-g09d2 From 86f8353ce7a343db7b19422db0d37379ac45ff4e Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 28 Nov 2024 09:55:57 +0100 Subject: Detect working gpgv using gpgv --dump-options This gains us support for not using an absolute path, which is nice, and we can now cache the invocation across fork() in the method. Still keep preferring absolute paths, this avoids execvp() having to iterate the path and reduces bug potential with custom gpgv in /usr/local. Fix the last entry in the list of gpgvs to say gpgv1 --- apt-pkg/contrib/gpgv.cc | 100 +++++++++++++++++++++++++++--------------------- apt-pkg/contrib/gpgv.h | 8 ++++ methods/gpgv.cc | 6 +++ 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/apt-pkg/contrib/gpgv.cc b/apt-pkg/contrib/gpgv.cc index 611f4bfa7..43c634dde 100644 --- a/apt-pkg/contrib/gpgv.cc +++ b/apt-pkg/contrib/gpgv.cc @@ -18,11 +18,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include @@ -138,6 +140,54 @@ static void APT_PRINTF(5) apt_msg(std::string const &tag, std::ostream &outterm, outterm << errtext << std::flush; } } + +static bool CheckGPGV(std::unordered_map> &checkedCommands, std::string gpgv, bool Debug) +{ + if (checkedCommands.find(gpgv) == checkedCommands.end()) + { + // Create entry + checkedCommands[gpgv]; + FileFd dumpOptions; + pid_t child; + const char *argv[] = {gpgv.c_str(), "--dump-options", nullptr}; + if (unlikely(Debug)) + std::clog << "Executing " << gpgv << " --dump-options" << std::endl; + if (not Popen(argv, dumpOptions, child, FileFd::ReadOnly) && Debug) + return false; + + for (std::string line; dumpOptions.ReadLine(line);) + { + if (unlikely(Debug)) + std::clog << "Read line: " << line << std::endl; + checkedCommands[gpgv].push_front(APT::String::Strip(line)); + } + dumpOptions.Close(); + waitpid(child, NULL, 0); + } + return not checkedCommands[gpgv].empty(); +} + +std::pair> APT::Internal::FindGPGV(bool Debug) +{ + static thread_local std::unordered_map> checkedCommands; + const std::string gpgvVariants[] = { + _config->Find("Apt::Key::gpgvcommand"), + // Prefer absolute path + "/usr/bin/gpgv-sq", + "/usr/bin/gpgv", + "/usr/bin/gpgv2", + "/usr/bin/gpgv1", + "gpgv-sq", + "gpgv", + "gpgv2", + "gpgv1", + }; + for (auto gpgv : gpgvVariants) + if (CheckGPGV(checkedCommands, gpgv, Debug)) + return std::make_pair(gpgv, checkedCommands[gpgv]); + return {}; +} + void ExecGPGV(std::string const &File, std::string const &FileGPG, int const &statusfd, int fd[2], std::string const &key) { @@ -148,13 +198,7 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, void ExecGPGV(std::string const &File, std::string const &FileGPG, int const &statusfd, int fd[2], std::vector const &KeyFiles) { - #define EINTERNAL 111 - const std::string gpgvVariants[] = { - "/usr/bin/gpgv-sq", - "/usr/bin/gpgv", - "/usr/bin/gpgv2", - "/usr/bin/gpgv", - }; +#define EINTERNAL 111 bool const Debug = _config->FindB("Debug::Acquire::gpgv", false); struct exiter { std::vector files; @@ -165,24 +209,11 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, } } local_exit; - std::string gpgv = _config->Find("Apt::Key::gpgvcommand"); - if (gpgv.empty() || !FileExists(gpgv)) + auto [gpgv, supportedOptions] = APT::Internal::FindGPGV(Debug); + if (gpgv.empty()) { - gpgv = ""; - for (auto gpgvVariant : gpgvVariants) - { - if (FileExists(gpgvVariant)) - { - gpgv = gpgvVariant; - break; - } - } - - if (gpgv.empty()) - { - apt_error(std::cerr, statusfd, fd, "Couldn't find a gpgv binary"); - local_exit(EINTERNAL); - } + apt_error(std::cerr, statusfd, fd, "Couldn't find a gpgv binary"); + local_exit(EINTERNAL); } std::vector Args; @@ -303,25 +334,8 @@ void ExecGPGV(std::string const &File, std::string const &FileGPG, if (auto assertPubkeyAlgo = _config->Find("Apt::Key::assert-pubkey-algo"); not assertPubkeyAlgo.empty()) { - FileFd dumpOptions; - pid_t child; - if (Debug) - std::clog << "Calling " << gpgv << " --dump-options" << std::endl; - const char* argv[] = {gpgv.c_str(), "--dump-options", nullptr}; - if (not Popen(argv, dumpOptions, child, FileFd::ReadOnly) && Debug) - std::clog << "Failed to call " << gpgv << " --dump-options" << std::endl; - for (std::string line; dumpOptions.ReadLine(line); ) - { - if (Debug) - std::clog << "Read line: " << line << std::endl; - if (APT::String::Strip(line) == "--assert-pubkey-algo") - { - Args.push_back("--assert-pubkey-algo=" + assertPubkeyAlgo); - break; - } - } - dumpOptions.Close(); - waitpid(child, NULL, 0); + if (std::find(supportedOptions.begin(), supportedOptions.end(), "--assert-pubkey-algo") != supportedOptions.end()) + Args.push_back("--assert-pubkey-algo=" + assertPubkeyAlgo); } Configuration::Item const *Opts; diff --git a/apt-pkg/contrib/gpgv.h b/apt-pkg/contrib/gpgv.h index 62c92f271..d4520f3a2 100644 --- a/apt-pkg/contrib/gpgv.h +++ b/apt-pkg/contrib/gpgv.h @@ -11,12 +11,20 @@ #include +#include #include #include class FileFd; +#ifdef APT_COMPILING_APT +namespace APT::Internal +{ +APT_PUBLIC std::pair> FindGPGV(bool Debug); +} +#endif + /** \brief generates and run the command to verify a file with gpgv * * If File and FileSig specify the same file it is assumed that we diff --git a/methods/gpgv.cc b/methods/gpgv.cc index 9f2ccb174..947b585da 100644 --- a/methods/gpgv.cc +++ b/methods/gpgv.cc @@ -190,6 +190,12 @@ string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile, if (Debug == true) std::clog << "inside VerifyGetSigners" << std::endl; + // Abort early if we can't find gpgv, before forking further. This also + // caches the invocation of --dump-options to find the working gpgv across + // our fork() below. + if (APT::Internal::FindGPGV(Debug).first.empty()) + return "Internal error: Cannot find gpgv"; + int fd[2]; if (pipe(fd) < 0) -- cgit v1.2.3-70-g09d2