/** * Copyright (C) 2021 Saturneric * * This file is part of GpgFrontend. * * GpgFrontend is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GpgFrontend is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GpgFrontend. If not, see . * * The initial version of the source code is inherited from * the gpg4usb project, which is under GPL-3.0-or-later. * * All the source code of GpgFrontend was modified and released by * Saturneric starting on May 12, 2021. * * SPDX-License-Identifier: GPL-3.0-or-later * */ #include "GnuPGInfoGatheringModule.h" #include #include #include #include // qt #include #include #include #include // c++ #include #include "GpgInfo.h" template <> struct fmt::formatter { // Parses format specifications. constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.begin(); } // Formats the QString qstr and writes it to the output. template auto format(const QString &qstr, FormatContext &ctx) const -> decltype(ctx.out()) { // Convert QString to UTF-8 QString (to handle Unicode characters // correctly) QByteArray utf8_array = qstr.toUtf8(); return fmt::format_to(ctx.out(), "{}", utf8_array.constData()); } }; template <> struct fmt::formatter { // Parses format specifications. constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.begin(); } // Formats the QString qstr and writes it to the output. template auto format(const QByteArray &qarray, FormatContext &ctx) const -> decltype(ctx.out()) { // Convert QString to UTF-8 QString (to handle Unicode characters // correctly) return fmt::format_to(ctx.out(), "{}", qarray.constData()); } }; extern auto CalculateBinaryChacksum(const QString &path) -> std::optional; extern void GetGpgComponentInfos(void *, int, const char *, const char *); extern void GetGpgDirectoryInfos(void *, int, const char *, const char *); extern void GetGpgOptionInfos(void *, int, const char *, const char *); using Context = struct { QString gpgme_version; QString gpgconf_path; GpgComponentInfo component_info; }; auto GFGetModuleGFSDKVersion() -> const char * { return GFModuleStrDup(GF_SDK_VERSION_STR); } auto GFGetModuleQtEnvVersion() -> const char * { return GFModuleStrDup(QT_VERSION_STR); } auto GFGetModuleID() -> const char * { return GFModuleStrDup("com.bktus.gpgfrontend.module.gnupg_info_gathering"); } auto GFGetModuleVersion() -> const char * { return GFModuleStrDup("1.0.0"); } auto GFGetModuleMetaData() -> GFModuleMetaData * { auto *p_meta = static_cast( GFAllocateMemory(sizeof(GFModuleMetaData))); auto *h_meta = p_meta; p_meta->key = "Name"; p_meta->value = "GatherGnupgInfo"; p_meta->next = static_cast( GFAllocateMemory(sizeof(GFModuleMetaData))); p_meta = p_meta->next; p_meta->key = "Description"; p_meta->value = "Try gathering gnupg informations"; p_meta->next = static_cast( GFAllocateMemory(sizeof(GFModuleMetaData))); p_meta = p_meta->next; p_meta->key = "Author"; p_meta->value = "Saturneric"; p_meta->next = nullptr; return h_meta; } auto GFRegisterModule() -> int { GFModuleLogDebug("gnupg info gathering module registering"); return 0; } auto GFActiveModule() -> int { GFModuleLogDebug("gnupg info gathering module activating"); GFModuleListenEvent(GFGetModuleID(), GFModuleStrDup("REQUEST_GATHERING_GNUPG_INFO")); return 0; } auto GFExecuteModule(GFModuleEvent *event) -> int { GFModuleLogDebug( fmt::format("gnupg info gathering module executing, event id: {}", event->id) .c_str()); GFModuleLogDebug("start to load extra info at module gnupginfogathering..."); const auto *const gpgme_version = GFModuleRetrieveRTValueOrDefault( GFModuleStrDup("core"), GFModuleStrDup("gpgme.version"), GFModuleStrDup("0.0.0")); GFModuleLogDebug( fmt::format("got gpgme version from rt: {}", gpgme_version).c_str()); const auto *const gpgconf_path = GFModuleRetrieveRTValueOrDefault( GFModuleStrDup("core"), GFModuleStrDup("gpgme.ctx.gpgconf_path"), GFModuleStrDup("")); GFModuleLogDebug( fmt::format("got gpgconf path from rt: {}", gpgconf_path).c_str()); auto context = Context{gpgme_version, gpgconf_path}; // get all components const char *argv[] = {GFModuleStrDup("--list-components")}; GFExecuteCommandSync(gpgconf_path, 1, argv, GetGpgComponentInfos, &context); GFModuleLogDebug("load gnupg component info done."); #ifdef QT5_BUILD QVector exec_contexts; #else QList exec_contexts; #endif const char **argv_0 = static_cast(GFAllocateMemory(sizeof(const char *))); argv_0[0] = GFModuleStrDup("--list-dirs"); exec_contexts.push_back( {gpgconf_path, 1, argv_0, GetGpgDirectoryInfos, nullptr}); char **components_c_array; int ret = GFModuleListRTChildKeys( GFGetModuleID(), GFModuleStrDup("gnupg.components"), &components_c_array); if (components_c_array == nullptr || ret == 0) return -1; QStringList components; auto *p_a = components_c_array; for (int i = 0; i < ret; i++) components.append(QString::fromUtf8(p_a[i])); for (const auto &component : components) { const auto *component_info_json = GFModuleRetrieveRTValueOrDefault( GFGetModuleID(), GFModuleStrDup(QString("gnupg.components.%1").arg(component).toUtf8()), nullptr); if (component_info_json == nullptr) continue; auto jsonlized_component_info = QJsonDocument::fromJson(component_info_json); assert(jsonlized_component_info.isObject()); auto component_info = GpgComponentInfo(jsonlized_component_info.object()); GFModuleLogDebug(fmt::format("gpgconf check options ready, " "component: {}", component_info.name) .c_str()); if (component_info.name == "gpgme" || component_info.name == "gpgconf") { continue; } auto *context = new (GFAllocateMemory(sizeof(Context))) Context{gpgme_version, gpgconf_path, component_info}; const char **argv_0 = static_cast(GFAllocateMemory(sizeof(const char *) * 2)); argv_0[0] = GFModuleStrDup("--list-options"), argv_0[1] = GFModuleStrDup(component_info.name.toUtf8()); exec_contexts.push_back( {gpgconf_path, 2, argv_0, GetGpgOptionInfos, context}); } GFExecuteCommandBatchSync(static_cast(exec_contexts.size()), exec_contexts.constData()); GFModuleUpsertRTValueBool(GFGetModuleID(), GFModuleStrDup("gnupg.gathering_done"), 1); char **event_argv = static_cast(GFAllocateMemory(sizeof(char **) * 1)); event_argv[0] = GFModuleStrDup("0"); GFModuleTriggerModuleEventCallback(event, GFGetModuleID(), 1, event_argv); GFModuleLogDebug("gnupg external info gathering done"); return 0; } auto GFDeactiveModule() -> int { return 0; } auto GFUnregisterModule() -> int { GFModuleLogDebug("gnupg info gathering module unregistering"); return 0; } auto CalculateBinaryChacksum(const QString &path) -> std::optional { // check file info and access rights QFileInfo info(path); if (!info.exists() || !info.isFile() || !info.isReadable()) { GFModuleLogError(fmt::format("get info for file {} error, exists: {}", info.filePath(), info.exists()) .c_str()); return {}; } // open and read file QFile f(info.filePath()); if (!f.open(QIODevice::ReadOnly)) { GFModuleLogError(fmt::format("open {} to calculate checksum error: {}", path.toStdString(), f.errorString().toStdString()) .c_str()); return {}; } QCryptographicHash hash_sha(QCryptographicHash::Sha256); // read data by chunks const qint64 buffer_size = 8192; // Define a suitable buffer size while (!f.atEnd()) { QByteArray const buffer = f.read(buffer_size); if (buffer.isEmpty()) { GFModuleLogError(fmt::format("error reading file {} during " "checksum calculation", path.toStdString()) .c_str()); return {}; } hash_sha.addData(buffer); } // close the file f.close(); // return the first 6 characters of the SHA-256 hash // of the file return QString(hash_sha.result().toHex()).left(6); } void GetGpgComponentInfos(void *data, int exit_code, const char *out, const char *err) { auto *context = reinterpret_cast(data); auto p_out = QString::fromUtf8(out); auto p_err = QString::fromUtf8(err); GFModuleLogDebug(fmt::format("gpgconf components exit_code: {} " "process stdout size: {}", exit_code, p_out.size()) .c_str()); if (exit_code != 0) { GFModuleLogError(fmt::format("gpgconf execute error, process " "stderr: {}, " "process stdout: {}", p_err, p_out) .c_str()); return; } std::vector component_infos; GpgComponentInfo c_i_gpgme; c_i_gpgme.name = "gpgme"; c_i_gpgme.desc = "GPG Made Easy"; c_i_gpgme.version = context->gpgme_version; c_i_gpgme.path = "Embedded In"; c_i_gpgme.binary_checksum = "/"; GpgComponentInfo c_i_gpgconf; c_i_gpgconf.name = "gpgconf"; c_i_gpgconf.desc = "GPG Configure"; c_i_gpgconf.version = "/"; c_i_gpgconf.path = context->gpgconf_path; auto gpgconf_binary_checksum = CalculateBinaryChacksum(context->gpgconf_path); c_i_gpgconf.binary_checksum = (gpgconf_binary_checksum.has_value() ? gpgconf_binary_checksum.value() : QString("/")); component_infos.push_back(c_i_gpgme); component_infos.push_back(c_i_gpgconf); auto const jsonlized_gpgme_component_info = c_i_gpgme.Json(); auto const jsonlized_gpgconf_component_info = c_i_gpgconf.Json(); GFModuleUpsertRTValue( GFGetModuleID(), GFModuleStrDup("gnupg.components.gpgme"), GFModuleStrDup(QJsonDocument(jsonlized_gpgme_component_info).toJson())); GFModuleUpsertRTValue( GFGetModuleID(), GFModuleStrDup("gnupg.components.gpgconf"), GFModuleStrDup(QJsonDocument(jsonlized_gpgconf_component_info).toJson())); auto line_split_list = p_out.split("\n"); for (const auto &line : line_split_list) { auto info_split_list = line.split(":"); if (info_split_list.size() != 3) continue; auto component_name = info_split_list[0].trimmed(); auto component_desc = info_split_list[1].trimmed(); auto component_path = info_split_list[2].trimmed(); #ifdef WINDOWS // replace some special substrings on windows // platform component_path.replace("%3a", ":"); #endif auto binary_checksum = CalculateBinaryChacksum(component_path); GFModuleLogDebug( fmt::format("gnupg component name: {} desc: " "{} checksum: {} path: {} ", component_name, component_desc, binary_checksum.has_value() ? binary_checksum.value() : "/", component_path) .c_str()); QString version = "/"; if (component_name == "gpg") { version = GFModuleRetrieveRTValueOrDefault( GFModuleStrDup("core"), GFModuleStrDup("gpgme.ctx.gnupg_version"), GFModuleStrDup("2.0.0")); } if (component_name == "gpg-agent") { GFModuleUpsertRTValue(GFGetModuleID(), GFModuleStrDup("gnupg.gpg_agent_path"), GFModuleStrDup(QString(component_path).toUtf8())); } if (component_name == "dirmngr") { GFModuleUpsertRTValue(GFGetModuleID(), GFModuleStrDup("gnupg.dirmngr_path"), GFModuleStrDup(QString(component_path).toUtf8())); } if (component_name == "keyboxd") { GFModuleUpsertRTValue(GFGetModuleID(), GFModuleStrDup("gnupg.keyboxd_path"), GFModuleStrDup(QString(component_path).toUtf8())); } { GpgComponentInfo c_i; c_i.name = component_name; c_i.desc = component_desc; c_i.version = version; c_i.path = component_path; c_i.binary_checksum = (binary_checksum.has_value() ? binary_checksum.value() : QString("/")); auto const jsonlized_component_info = c_i.Json(); GFModuleUpsertRTValue( GFGetModuleID(), GFModuleStrDup( QString("gnupg.components.%1").arg(component_name).toUtf8()), GFModuleStrDup(QJsonDocument(jsonlized_component_info).toJson())); component_infos.push_back(c_i); } GFModuleLogDebug("load gnupg component info actually done."); } } void GetGpgDirectoryInfos(void *, int exit_code, const char *out, const char *err) { if (exit_code != 0) return; auto p_out = QString::fromUtf8(out); auto p_err = QString::fromUtf8(err); auto line_split_list = p_out.split("\n"); for (const auto &line : line_split_list) { auto info_split_list = line.split(":"); GFModuleLogDebug(fmt::format("gpgconf direcrotries info line: " "{} info size: {}", line, info_split_list.size()) .c_str()); if (info_split_list.size() != 2) continue; auto configuration_name = info_split_list[0].trimmed(); auto configuration_value = info_split_list[1].trimmed(); #ifdef WINDOWS // replace some special substrings on windows // platform configuration_value.replace("%3a", ":"); #endif // record gnupg home path if (configuration_name == "homedir") { GFModuleUpsertRTValue(GFGetModuleID(), GFModuleStrDup("gnupg.home_path"), GFModuleStrDup(configuration_value.toUtf8())); } GFModuleUpsertRTValue( GFGetModuleID(), GFModuleStrDup( QString("gnupg.dirs.%1").arg(configuration_name).toUtf8()), GFModuleStrDup(configuration_value.toUtf8())); } } void GetGpgOptionInfos(void *data, int exit_code, const char *out, const char *err) { if (exit_code != 0) return; auto p_out = QString::fromUtf8(out); auto p_err = QString::fromUtf8(err); auto *context = reinterpret_cast(data); auto component_name = context->component_info.name; GFModuleLogDebug(fmt::format("gpgconf {} avaliable options " "exit_code: {} process stdout " "size: {} ", component_name, exit_code, p_out.size()) .c_str()); std::vector options_infos; auto line_split_list = p_out.split("\n"); for (const auto &line : line_split_list) { auto info_split_list = line.split(":"); GFModuleLogDebug(fmt::format("component {} avaliable options " "line: {} info size: {}", component_name, line, info_split_list.size()) .c_str()); if (info_split_list.size() < 10) continue; // The format of each line is: // name:flags:level:description:type:alt-type:argname:default:argdef:value auto option_name = info_split_list[0].trimmed(); auto option_flags = info_split_list[1].trimmed(); auto option_level = info_split_list[2].trimmed(); auto option_desc = info_split_list[3].trimmed(); auto option_type = info_split_list[4].trimmed(); auto option_alt_type = info_split_list[5].trimmed(); auto option_argname = info_split_list[6].trimmed(); auto option_default = info_split_list[7].trimmed(); auto option_argdef = info_split_list[8].trimmed(); auto option_value = info_split_list[9].trimmed(); GpgOptionsInfo info; info.name = option_name; info.flags = option_flags; info.level = option_level; info.description = option_desc; info.type = option_type; info.alt_type = option_alt_type; info.argname = option_argname; info.default_value = option_default; info.argdef = option_argdef; info.value = option_value; auto const jsonlized_option_info = info.Json(); GFModuleUpsertRTValue( GFGetModuleID(), GFModuleStrDup(QString("gnupg.components.%1.options.%2") .arg(component_name) .arg(option_name) .toUtf8()), GFModuleStrDup(QJsonDocument(jsonlized_option_info).toJson())); options_infos.push_back(info); } context->~Context(); GFFreeMemory(context); }