diff options
Diffstat (limited to 'src/core/GpgCoreInit.cpp')
-rw-r--r-- | src/core/GpgCoreInit.cpp | 575 |
1 files changed, 392 insertions, 183 deletions
diff --git a/src/core/GpgCoreInit.cpp b/src/core/GpgCoreInit.cpp index 6d782439..f231056f 100644 --- a/src/core/GpgCoreInit.cpp +++ b/src/core/GpgCoreInit.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021 Saturneric + * Copyright (C) 2021 Saturneric <[email protected]> * * This file is part of GpgFrontend. * @@ -20,236 +20,445 @@ * the gpg4usb project, which is under GPL-3.0-or-later. * * All the source code of GpgFrontend was modified and released by - * Saturneric<[email protected]> starting on May 12, 2021. + * Saturneric <[email protected]> starting on May 12, 2021. * * SPDX-License-Identifier: GPL-3.0-or-later * */ #include "GpgCoreInit.h" -#include <spdlog/async.h> -#include <spdlog/common.h> -#include <spdlog/sinks/rotating_file_sink.h> -#include <spdlog/sinks/stdout_color_sinks.h> +#include <gpgme.h> -#include <filesystem> -#include <string> - -#include "GpgFunctionObject.h" -#include "core/GpgContext.h" +#include "core/function/CoreSignalStation.h" #include "core/function/GlobalSettingStation.h" -#include "function/gpg/GpgAdvancedOperator.h" -#include "spdlog/spdlog.h" -#include "thread/Task.h" -#include "thread/TaskRunner.h" -#include "thread/TaskRunnerGetter.h" +#include "core/function/basic/ChannelObject.h" +#include "core/function/basic/SingletonStorage.h" +#include "core/function/gpg/GpgAdvancedOperator.h" +#include "core/function/gpg/GpgContext.h" +#include "core/function/gpg/GpgKeyGetter.h" +#include "core/module/ModuleManager.h" +#include "core/thread/Task.h" +#include "core/thread/TaskRunner.h" +#include "core/thread/TaskRunnerGetter.h" +#include "core/utils/CommonUtils.h" +#include "core/utils/GpgUtils.h" +#include "core/utils/MemoryUtils.h" namespace GpgFrontend { -/** - * @brief setup logging system and do proper initialization - * - */ -void InitCoreLoggingSystem() { - using namespace boost::posix_time; - using namespace boost::gregorian; - - // get the log directory - auto logfile_path = - (GlobalSettingStation::GetInstance().GetLogDir() / "core"); - logfile_path.replace_extension(".log"); - - // sinks - std::vector<spdlog::sink_ptr> sinks; - sinks.push_back(std::make_shared<spdlog::sinks::stderr_color_sink_mt>()); - sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>( - logfile_path.u8string(), 1048576 * 32, 8)); - - // thread pool - spdlog::init_thread_pool(1024, 2); - - // logger - auto core_logger = std::make_shared<spdlog::async_logger>( - "core", begin(sinks), end(sinks), spdlog::thread_pool()); - core_logger->set_pattern( - "[%H:%M:%S.%e] [T:%t] [%=4n] %^[%=8l]%$ [%s:%#] [%!] -> %v (+%ius)"); - -#ifdef DEBUG - core_logger->set_level(spdlog::level::trace); -#else - core_logger->set_level(spdlog::level::info); -#endif +void DestroyGpgFrontendCore() { SingletonStorageCollection::Destroy(); } - // flush policy - core_logger->flush_on(spdlog::level::err); - spdlog::flush_every(std::chrono::seconds(5)); +auto VerifyGpgconfPath(const QFileInfo& gnupg_install_fs_path) -> bool { + return gnupg_install_fs_path.isAbsolute() && gnupg_install_fs_path.exists() && + gnupg_install_fs_path.isFile(); +} - // register it as default logger - spdlog::set_default_logger(core_logger); +auto VerifyKeyDatabasePath(const QFileInfo& key_database_fs_path) -> bool { + return key_database_fs_path.isAbsolute() && key_database_fs_path.exists() && + key_database_fs_path.isDir(); } -void ShutdownCoreLoggingSystem() { -#ifdef WINDOWS - // Under VisualStudio, this must be called before main finishes to workaround - // a known VS issue - spdlog::drop_all(); - spdlog::shutdown(); -#endif +auto SearchGpgconfPath(const QList<QString>& candidate_paths) -> QString { + for (const auto& path : candidate_paths) { + if (VerifyGpgconfPath(QFileInfo(path))) { + return path; + } + } + return {}; } -void ResetGpgFrontendCore() { reset_gpgfrontend_core(); } +auto SearchKeyDatabasePath(const QList<QString>& candidate_paths) -> QString { + for (const auto& path : candidate_paths) { + GF_CORE_LOG_DEBUG("searh for candidate key database path: {}", path); + if (VerifyKeyDatabasePath(QFileInfo(path))) { + return path; + } + } + return {}; +} + +auto InitGpgME(const QString& gnupg_path) -> bool { + // init gpgme subsystem and get gpgme library version + Module::UpsertRTValue("core", "gpgme.version", + QString(gpgme_check_version(nullptr))); -void init_gpgfrontend_core() { - /* Initialize the locale environment. */ - SPDLOG_DEBUG("locale: {}", setlocale(LC_CTYPE, nullptr)); - // init gpgme subsystem - gpgme_check_version(nullptr); gpgme_set_locale(nullptr, LC_CTYPE, setlocale(LC_CTYPE, nullptr)); #ifdef LC_MESSAGES gpgme_set_locale(nullptr, LC_MESSAGES, setlocale(LC_MESSAGES, nullptr)); #endif - // read settings - bool forbid_all_gnupg_connection = - GlobalSettingStation::GetInstance().LookupSettings( - "network.forbid_all_gnupg_connection", false); + if (!gnupg_path.isEmpty()) { + GF_CORE_LOG_DEBUG("gpgme set engine info, gnupg path: {}", gnupg_path); + CheckGpgError(gpgme_set_engine_info(GPGME_PROTOCOL_OPENPGP, + gnupg_path.toUtf8(), nullptr)); + } + + gpgme_ctx_t p_ctx; + CheckGpgError(gpgme_new(&p_ctx)); + + // get engine info + auto* engine_info = gpgme_ctx_get_engine_info(p_ctx); + // Check ENV before running + bool find_openpgp = false; + bool find_gpgconf = false; + bool find_cms = false; + + while (engine_info != nullptr) { + if (strcmp(engine_info->version, "1.0.0") == 0) { + engine_info = engine_info->next; + continue; + } + + GF_CORE_LOG_DEBUG( + "gpg context engine info: {} {} {} {}", + gpgme_get_protocol_name(engine_info->protocol), + QString(engine_info->file_name == nullptr ? "null" + : engine_info->file_name), + QString(engine_info->home_dir == nullptr ? "null" + : engine_info->home_dir), + QString(engine_info->version ? "null" : engine_info->version)); + + switch (engine_info->protocol) { + case GPGME_PROTOCOL_OpenPGP: + find_openpgp = true; + + Module::UpsertRTValue("core", "gpgme.engine.openpgp", 1); + Module::UpsertRTValue("core", "gpgme.ctx.app_path", + QString(engine_info->file_name)); + Module::UpsertRTValue("core", "gpgme.ctx.gnupg_version", + QString(engine_info->version)); + Module::UpsertRTValue( + "core", "gpgme.ctx.database_path", + QString(engine_info->home_dir == nullptr ? "" + : engine_info->home_dir)); + break; + case GPGME_PROTOCOL_CMS: + find_cms = true; + Module::UpsertRTValue("core", "gpgme.engine.cms", 1); + Module::UpsertRTValue("core", "gpgme.ctx.cms_path", + QString(engine_info->file_name)); + + break; + case GPGME_PROTOCOL_GPGCONF: + find_gpgconf = true; + + Module::UpsertRTValue("core", "gpgme.engine.gpgconf", 1); + Module::UpsertRTValue("core", "gpgme.ctx.gpgconf_path", + QString(engine_info->file_name)); + break; + case GPGME_PROTOCOL_ASSUAN: + + Module::UpsertRTValue("core", "gpgme.engine.assuan", 1); + Module::UpsertRTValue("core", "gpgme.ctx.assuan_path", + QString(engine_info->file_name)); + break; + case GPGME_PROTOCOL_G13: + break; + case GPGME_PROTOCOL_UISERVER: + break; + case GPGME_PROTOCOL_SPAWN: + break; + case GPGME_PROTOCOL_DEFAULT: + break; + case GPGME_PROTOCOL_UNKNOWN: + break; + } + engine_info = engine_info->next; + } + + // release gpgme context + gpgme_release(p_ctx); + + const auto gnupg_version = Module::RetrieveRTValueTypedOrDefault<>( + "core", "gpgme.ctx.gnupg_version", QString{"0.0.0"}); + GF_CORE_LOG_DEBUG("got gnupg version from rt: {}", gnupg_version); - bool auto_import_missing_key = - GlobalSettingStation::GetInstance().LookupSettings( - "network.auto_import_missing_key", false); + // conditional check: only support gpg 2.1.x now + if (!(CompareSoftwareVersion(gnupg_version, "2.1.0") >= 0 && find_gpgconf && + find_openpgp && find_cms)) { + GF_CORE_LOG_ERROR("gpgme env check failed, abort"); + return false; + } + + Module::UpsertRTValue("core", "env.state.gpgme", 1); + return true; +} - bool use_custom_key_database_path = - GlobalSettingStation::GetInstance().LookupSettings( - "general.use_custom_key_database_path", false); +auto GetGnuPGPathByGpgConf(const QString& gnupg_install_fs_path) -> QString { + auto* process = new QProcess(); + process->setProgram(gnupg_install_fs_path); + process->start(); + process->waitForFinished(1000); + auto output_buffer = process->readAllStandardOutput(); + process->deleteLater(); - std::string custom_key_database_path = - GlobalSettingStation::GetInstance().LookupSettings( - "general.custom_key_database_path", std::string{}); + if (output_buffer.isEmpty()) return {}; - bool use_custom_gnupg_install_path = - GlobalSettingStation::GetInstance().LookupSettings( - "general.use_custom_gnupg_install_path", false); + auto line_split_list = QString(output_buffer).split("\n"); + for (const auto& line : line_split_list) { + auto info_split_list = line.split(":"); - std::string custom_gnupg_install_path = - GlobalSettingStation::GetInstance().LookupSettings( - "general.custom_gnupg_install_path", std::string{}); + if (info_split_list.size() != 3) continue; - bool use_pinentry_as_password_input_dialog = - GpgFrontend::GlobalSettingStation::GetInstance().LookupSettings( - "general.use_pinentry_as_password_input_dialog", false); + auto component_name = info_split_list[0].trimmed(); + auto component_desc = info_split_list[1].trimmed(); + auto component_path = info_split_list[2].trimmed(); - SPDLOG_DEBUG("core loaded if use custom key databse path: {}", - use_custom_key_database_path); - SPDLOG_DEBUG("core loaded custom key databse path: {}", - custom_key_database_path); + if (component_name.toLower() == "gpg") { +#ifdef WINDOWS + // replace some special substrings on windows platform + component_path.replace("%3a", ":"); +#endif + QFileInfo file_info(component_path); + if (file_info.exists() && file_info.isFile()) { + return file_info.absoluteFilePath(); + } + return {}; + } + } + return ""; +} - // check gpgconf path - std::filesystem::path custom_gnupg_install_fs_path = - custom_gnupg_install_path; +auto DetectGnuPGPath() -> QString { + auto settings = GlobalSettingStation::GetInstance().GetSettings(); + auto use_custom_gnupg_install_path = + settings.value("basic/use_custom_gnupg_install_path", false).toBool(); + auto custom_gnupg_install_path = + settings.value("basic/custom_gnupg_install_path", QString{}).toString(); + + QString gnupg_install_fs_path; + // user defined + if (use_custom_gnupg_install_path && !custom_gnupg_install_path.isEmpty()) { + // check gpgconf path + gnupg_install_fs_path = custom_gnupg_install_path; #ifdef WINDOWS - custom_gnupg_install_fs_path /= "gpgconf.exe"; + gnupg_install_fs_path += "/gpgconf.exe"; #else - custom_gnupg_install_fs_path /= "gpgconf"; + gnupg_install_fs_path += "/gpgconf"; #endif - if (!custom_gnupg_install_fs_path.is_absolute() || - !std::filesystem::exists(custom_gnupg_install_fs_path) || - !std::filesystem::is_regular_file(custom_gnupg_install_fs_path)) { - use_custom_gnupg_install_path = false; - SPDLOG_ERROR("core loaded custom gpgconf path is illegal: {}", - custom_gnupg_install_fs_path.u8string()); - } else { - SPDLOG_DEBUG("core loaded custom gpgconf path: {}", - custom_gnupg_install_fs_path.u8string()); + if (!VerifyGpgconfPath(QFileInfo(gnupg_install_fs_path))) { + GF_CORE_LOG_ERROR("core loaded custom gpgconf path is illegal: {}", + gnupg_install_fs_path); + gnupg_install_fs_path = ""; + } } - // check key database path - std::filesystem::path custom_key_database_fs_path = custom_key_database_path; - if (!custom_key_database_fs_path.is_absolute() || - !std::filesystem::exists(custom_key_database_fs_path) || - !std::filesystem::is_directory(custom_key_database_fs_path)) { - use_custom_key_database_path = false; - SPDLOG_ERROR("core loaded custom gpg key database is illegal: {}", - custom_key_database_fs_path.u8string()); - } else { - SPDLOG_DEBUG("core loaded custom gpg key database path: {}", - custom_key_database_fs_path.u8string()); + // fallback to default path + if (gnupg_install_fs_path.isEmpty()) { +#ifdef MACOS + gnupg_install_fs_path = SearchGpgconfPath( + {"/usr/local/bin/gpgconf", "/opt/homebrew/bin/gpgconf"}); + GF_CORE_LOG_DEBUG("core loaded searched gpgconf path: {}", + gnupg_install_fs_path); +#endif + +#ifdef WINDOWS + gnupg_install_fs_path = + SearchGpgconfPath({"C:/Program Files (x86)/gnupg/bin"}); + GF_CORE_LOG_DEBUG("core loaded searched gpgconf path: {}", + gnupg_install_fs_path); +#endif } - // init default channel - auto& default_ctx = GpgFrontend::GpgContext::CreateInstance( - GPGFRONTEND_DEFAULT_CHANNEL, [=]() -> std::unique_ptr<ChannelObject> { - GpgFrontend::GpgContextInitArgs args; + if (!gnupg_install_fs_path.isEmpty()) { + return GetGnuPGPathByGpgConf( + QFileInfo(gnupg_install_fs_path).absoluteFilePath()); + } + return ""; +} - // set key database path - if (use_custom_key_database_path && !custom_key_database_path.empty()) { - args.db_path = custom_key_database_path; - } +void InitGpgFrontendCore(CoreInitArgs args) { + // initialize global register table + Module::UpsertRTValue("core", "env.state.gpgme", 0); + Module::UpsertRTValue("core", "env.state.ctx", 0); + Module::UpsertRTValue("core", "env.state.gnupg", 0); + Module::UpsertRTValue("core", "env.state.basic", 0); + Module::UpsertRTValue("core", "env.state.all", 0); + + // initialize locale environment + GF_CORE_LOG_DEBUG("locale: {}", setlocale(LC_CTYPE, nullptr)); + + auto gnupg_install_fs_path = DetectGnuPGPath(); + GF_CORE_LOG_INFO("detected gnupg path: {}", gnupg_install_fs_path); + + // initialize library gpgme + if (!InitGpgME(gnupg_install_fs_path)) { + CoreSignalStation::GetInstance()->SignalBadGnupgEnv( + QObject::tr("GpgME inilization failed")); + return; + } - if (use_custom_gnupg_install_path) { - args.custom_gpgconf = true; - args.custom_gpgconf_path = custom_gnupg_install_fs_path.u8string(); + auto* task = new Thread::Task( + [args, gnupg_install_fs_path](const DataObjectPtr&) -> int { + auto settings = GlobalSettingStation::GetInstance().GetSettings(); + // read settings from config file + auto forbid_all_gnupg_connection = + settings.value("network/forbid_all_gnupg_connection", false) + .toBool(); + + auto auto_import_missing_key = + settings.value("network/auto_import_missing_key", false).toBool(); + + auto use_custom_key_database_path = + settings.value("basic/use_custom_key_database_path", false) + .toBool(); + + auto custom_key_database_path = + settings.value("basic/custom_key_database_path", QString{}) + .toString(); + + auto custom_gnupg_install_path = + settings.value("basic/custom_gnupg_install_path", QString{}) + .toString(); + + auto use_pinentry_as_password_input_dialog = + settings.value("basic/use_pinentry_as_password_input_dialog", false) + .toBool(); + + GF_CORE_LOG_DEBUG("core loaded if use custom key databse path: {}", + use_custom_key_database_path); + GF_CORE_LOG_DEBUG("core loaded custom key databse path: {}", + custom_key_database_path); + + // check key database path + QString key_database_fs_path; + // user defined + if (use_custom_key_database_path && + !custom_key_database_path.isEmpty()) { + key_database_fs_path = custom_key_database_path; + if (VerifyKeyDatabasePath(QFileInfo(key_database_fs_path))) { + GF_CORE_LOG_ERROR( + "core loaded custom gpg key database is illegal: {}", + key_database_fs_path); + } else { + use_custom_key_database_path = true; + GF_CORE_LOG_DEBUG("core loaded custom gpg key database path: {}", + key_database_fs_path); + } + } else { +#if defined(LINUX) || defined(MACOS) + use_custom_key_database_path = true; + key_database_fs_path = + SearchKeyDatabasePath({QDir::home().path() + "/.gnupg"}); + GF_CORE_LOG_DEBUG("core loaded searched key database path: {}", + key_database_fs_path); +#endif } - args.offline_mode = forbid_all_gnupg_connection; - args.auto_import_missing_key = auto_import_missing_key; - args.use_pinentry = use_pinentry_as_password_input_dialog; - - return std::unique_ptr<ChannelObject>(new GpgContext(args)); - }); - - // exit if failed - if (!default_ctx.good()) { - SPDLOG_ERROR("default gnupg context init error"); - }; - - // async init no-ascii channel - Thread::TaskRunnerGetter::GetInstance() - .GetTaskRunner(Thread::TaskRunnerGetter::kTaskRunnerType_GPG) - ->PostTask( - new Thread::Task([=](Thread::Task::DataObjectPtr data_obj) -> int { - // init non-ascii channel - auto& ctx = GpgFrontend::GpgContext::CreateInstance( - GPGFRONTEND_NON_ASCII_CHANNEL, - [=]() -> std::unique_ptr<ChannelObject> { - GpgFrontend::GpgContextInitArgs args; - args.ascii = false; - - // set key database path - if (use_custom_key_database_path && - !custom_key_database_path.empty()) { - args.db_path = custom_key_database_path; + if (args.load_default_gpg_context) { + // init ctx, also checking the basical env + auto& ctx = GpgFrontend::GpgContext::CreateInstance( + kGpgFrontendDefaultChannel, [=]() -> ChannelObjectPtr { + GpgFrontend::GpgContextInitArgs args; + + // set key database path + if (use_custom_key_database_path && + !key_database_fs_path.isEmpty()) { + QFileInfo dir_info(key_database_fs_path); + if (dir_info.exists() && dir_info.isDir() && + dir_info.isReadable() && dir_info.isWritable()) { + args.db_path = dir_info.absoluteFilePath(); + GF_CORE_LOG_INFO("using key database path: {}", + args.db_path); + } else { + GF_CORE_LOG_ERROR( + "custom key database path: {}, is not point to " + "an accessible directory", + key_database_fs_path); } + } + + // set custom gnupg path + if (!gnupg_install_fs_path.isEmpty()) { + args.custom_gpgconf = true; + args.custom_gpgconf_path = gnupg_install_fs_path; + } + + args.offline_mode = forbid_all_gnupg_connection; + args.auto_import_missing_key = auto_import_missing_key; + args.use_pinentry = use_pinentry_as_password_input_dialog; + + return ConvertToChannelObjectPtr<>( + SecureCreateUniqueObject<GpgContext>( + args, kGpgFrontendDefaultChannel)); + }); + + // exit if failed + if (!ctx.Good()) { + GF_CORE_LOG_ERROR("default gnupg context init error, abort"); + CoreSignalStation::GetInstance()->SignalBadGnupgEnv( + QObject::tr("GpgME Context inilization failed")); + return -1; + } + Module::UpsertRTValue("core", "env.state.ctx", 1); + } - if (use_custom_gnupg_install_path) { - args.custom_gpgconf = true; - args.custom_gpgconf_path = - custom_gnupg_install_fs_path.u8string(); - } - - args.offline_mode = forbid_all_gnupg_connection; - args.auto_import_missing_key = auto_import_missing_key; - args.use_pinentry = use_pinentry_as_password_input_dialog; - - return std::unique_ptr<ChannelObject>(new GpgContext(args)); - }); - - if (!ctx.good()) SPDLOG_ERROR("no-ascii channel init error"); - return ctx.good() ? 0 : -1; - })); + if (args.load_default_gpg_context) { + if (!GpgKeyGetter::GetInstance().FlushKeyCache()) { + CoreSignalStation::GetInstance()->SignalBadGnupgEnv( + QObject::tr("Gpg Key Detabase inilization failed")); + }; + } + GF_CORE_LOG_DEBUG( + "basic env checking finished, " + "including gpgme, ctx, and key infos"); + Module::UpsertRTValue("core", "env.state.basic", 1); + CoreSignalStation::GetInstance()->SignalGoodGnupgEnv(); + + // if gnupg-info-gathering module activated + if (args.gather_external_gnupg_info && + Module::IsModuleAcivate("com.bktus.gpgfrontend.module." + "integrated.gnupg-info-gathering")) { + GF_CORE_LOG_DEBUG( + "module gnupg-info-gathering is activated, " + "loading external gnupg info..."); + + // gather external gnupg info + Module::TriggerEvent( + "GPGFRONTEND_CORE_INITLIZED", + [](const Module::EventIdentifier& /*e*/, + const Module::Event::ListenerIdentifier& l_id, + DataObjectPtr o) { + GF_CORE_LOG_DEBUG( + "received event GPGFRONTEND_CORE_INITLIZED callback " + "from module: {}", + l_id); + + if (l_id == + "com.bktus.gpgfrontend.module.integrated.gnupg-info-" + "gathering") { + GF_CORE_LOG_DEBUG( + "received callback from gnupg-info-gathering "); + + // try to restart all components + GpgFrontend::GpgAdvancedOperator::RestartGpgComponents(); + Module::UpsertRTValue("core", "env.state.gnupg", 1); + + // announce that all checkings were finished + GF_CORE_LOG_INFO( + "all env checking finished, including gpgme, " + "ctx and gnupg"); + Module::UpsertRTValue("core", "env.state.all", 1); + } + }); + } else { + GF_CORE_LOG_DEBUG("gnupg-info-gathering is not activated"); + Module::UpsertRTValue("core", "env.state.all", 1); + } + return 0; + }, + "core_init_task"); - // try to restart all components - GpgFrontend::GpgAdvancedOperator::GetInstance().RestartGpgComponents(); -} + QObject::connect(task, &Thread::Task::SignalTaskEnd, []() { -void reset_gpgfrontend_core() { SingletonStorageCollection::GetInstance(true); } + }); -void new_default_settings_channel(int channel) { - GpgFrontend::GpgContext::CreateInstance( - channel, [&]() -> std::unique_ptr<ChannelObject> { - GpgFrontend::GpgContextInitArgs args; - return std::unique_ptr<ChannelObject>(new GpgContext(args)); - }); + // start the thread to check ctx and gnupg state + // it may take a few seconds or minutes + GpgFrontend::Thread::TaskRunnerGetter::GetInstance() + .GetTaskRunner(Thread::TaskRunnerGetter::kTaskRunnerType_Default) + ->PostTask(task); } } // namespace GpgFrontend
\ No newline at end of file |