diff --git a/CMakeLists.txt b/CMakeLists.txt index 96fb5a6e4..59f092171 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,96 +1,96 @@ cmake_minimum_required(VERSION 3.5) -set(PIM_VERSION "5.13.41") +set(PIM_VERSION "5.13.42") project(libkleo VERSION ${PIM_VERSION}) set(KF5_MIN_VERSION "5.67.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(LIBRARY_NAMELINK) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMSetupVersion) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) set(LIBKLEO_LIB_VERSION ${PIM_VERSION}) set(QT_REQUIRED_VERSION "5.12.0") set(KDEPIMTEXTEDIT_VERSION "5.13.40") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5WidgetsAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Completion ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5PimTextEdit ${KDEPIMTEXTEDIT_VERSION} CONFIG) set(GPGMEPP_LIB_VERSION "1.11.1") find_package(Gpgmepp ${GPGMEPP_LIB_VERSION} CONFIG REQUIRED) set_package_properties(Gpgmepp PROPERTIES DESCRIPTION "GpgME++ Library" URL "https://www.gnupg.org" TYPE REQUIRED PURPOSE "GpgME++ is required for OpenPGP support") find_package(QGpgme ${GPGMEPP_LIB_VERSION} CONFIG REQUIRED) message(STATUS "GPGME Version ${Gpgmepp_VERSION}") find_package(Boost 1.34.0) set_package_properties(Boost PROPERTIES DESCRIPTION "Boost C++ Libraries" URL "https://www.boost.org" TYPE REQUIRED PURPOSE "Boost is required for building most KDEPIM applications") set_package_properties(KF5PimTextEdit PROPERTIES DESCRIPTION "A textedit with PIM-specific features." URL "https://commits.kde.org/kpimtextedit" TYPE OPTIONAL PURPOSE "Improved audit log viewer.") ecm_setup_version(PROJECT VARIABLE_PREFIX LIBKLEO VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/libkleo_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5LibkleoConfigVersion.cmake" SOVERSION 5 ) ########### Targets ########### add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0) #add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x060000) remove_definitions(-DQT_NO_CAST_FROM_ASCII ) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Libkleo") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5LibkleoConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5LibkleoConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5LibkleoConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5LibkleoConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5LibkleoTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5LibkleoTargets.cmake NAMESPACE KF5::) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libkleo_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) add_subdirectory(src) install(FILES libkleo.renamecategories libkleo.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a04dc3958..dc30079c6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,208 +1,211 @@ # target_include_directories does not handle empty include paths include_directories(${GPGME_INCLUDES}) add_definitions(-DTRANSLATION_DOMAIN=\"libkleopatra\") #add_definitions( -DQT_NO_CAST_FROM_ASCII ) #add_definitions( -DQT_NO_CAST_TO_ASCII ) kde_enable_exceptions() add_definitions( -DGPGMEPP_ERR_SOURCE_DEFAULT=13 ) # 13 is GPG_ERR_SOURCE_KLEO, even if gpg-error's too old to know about add_subdirectory( pics ) if (BUILD_TESTING) add_subdirectory( tests ) endif() ########### next target ############### set(libkleo_core_SRCS kleo/checksumdefinition.cpp kleo/defaultkeyfilter.cpp kleo/defaultkeygenerationjob.cpp kleo/dn.cpp kleo/enum.cpp kleo/exception.cpp kleo/kconfigbasedkeyfilter.cpp kleo/keyfiltermanager.cpp kleo/keyresolver.cpp models/keycache.cpp models/keylistmodel.cpp models/keylistsortfilterproxymodel.cpp models/keyrearrangecolumnsproxymodel.cpp models/subkeylistmodel.cpp models/useridlistmodel.cpp utils/filesystemwatcher.cpp utils/formatting.cpp utils/classify.cpp + utils/gnupg.cpp + utils/hex.cpp ) ecm_qt_declare_logging_category(libkleo_core_SRCS HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME org.kde.pim.libkleo) set(libkleo_ui_common_SRCS ui/dnattributeorderconfigwidget.cpp ui/kdhorizontalline.cpp ui/filenamerequester.cpp ui/messagebox.cpp ui/cryptoconfigmodule.cpp ui/cryptoconfigdialog.cpp ui/directoryserviceswidget.cpp ui/progressbar.cpp ui/progressdialog.cpp ui/auditlogviewer.cpp ) ecm_qt_declare_logging_category(libkleo_ui_common_SRCS HEADER kleo_ui_debug.h IDENTIFIER KLEO_UI_LOG CATEGORY_NAME org.kde.pim.kleo_ui) set(libkleo_ui_SRCS # make this a separate lib. ui/keylistview.cpp ui/keyselectiondialog.cpp ui/keyrequester.cpp ui/keyapprovaldialog.cpp ui/newkeyapprovaldialog.cpp ui/keyselectioncombo.cpp ) ki18n_wrap_ui(libkleo_ui_common_SRCS ui/directoryserviceswidget.ui ) set(kleo_LIB_SRCS ${libkleo_core_SRCS} ${libkleo_ui_SRCS} ${libkleo_ui_common_SRCS}) set(kleo_LIB_LIBS PUBLIC QGpgme Gpgmepp PRIVATE Qt5::Widgets KF5::I18n KF5::Completion KF5::ConfigCore KF5::CoreAddons KF5::WidgetsAddons KF5::ItemModels KF5::Codecs) if (KF5PimTextEdit_FOUND) add_definitions(-DHAVE_PIMTEXTEDIT) set(kleo_LIB_LIBS ${kleo_LIB_LIBS} PRIVATE KF5::PimTextEdit) endif() add_library(KF5Libkleo ${kleo_LIB_SRCS}) generate_export_header(KF5Libkleo BASE_NAME kleo) add_library(KF5::Libkleo ALIAS KF5Libkleo) if(WIN32) target_link_libraries(KF5Libkleo ${kleo_LIB_LIBS} ${GPGME_VANILLA_LIBRARIES} ) else() target_link_libraries(KF5Libkleo ${kleo_LIB_LIBS} ) endif() set_target_properties(KF5Libkleo PROPERTIES VERSION ${LIBKLEO_VERSION_STRING} SOVERSION ${LIBKLEO_SOVERSION} EXPORT_NAME Libkleo ) install(TARGETS KF5Libkleo EXPORT KF5LibkleoTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) target_include_directories(KF5Libkleo PUBLIC "$") target_include_directories(KF5Libkleo INTERFACE "$") ecm_generate_headers(libkleo_CamelCase_HEADERS HEADER_NAMES ChecksumDefinition DefaultKeyFilter DefaultKeyGenerationJob Dn Enum Exception KConfigBasedKeyFilter KeyFilter KeyFilterManager KeyResolver OidMap Predicates Stl_Util REQUIRED_HEADERS libkleo_HEADERS PREFIX Libkleo RELATIVE kleo ) ecm_generate_headers(libkleo_CamelCase_models_HEADERS HEADER_NAMES KeyCache KeyListModel KeyListModelInterface KeyListSortFilterProxyModel KeyRearrangeColumnsProxyModel SubkeyListModel UserIDListModel REQUIRED_HEADERS libkleo_models_HEADERS PREFIX Libkleo RELATIVE models ) ecm_generate_headers(libkleo_CamelCase_utils_HEADERS HEADER_NAMES Classify FileSystemWatcher Formatting + GnuPG REQUIRED_HEADERS libkleo_utils_HEADERS PREFIX Libkleo RELATIVE utils ) ecm_generate_headers(libkleo_CamelCase_ui_HEADERS HEADER_NAMES CryptoConfigDialog CryptoConfigModule DNAttributeOrderConfigWidget DirectoryServicesWidget FileNameRequester KDHorizontalLine KeyApprovalDialog NewKeyApprovalDialog KeyRequester KeySelectionCombo KeySelectionDialog MessageBox ProgressDialog REQUIRED_HEADERS libkleo_ui_HEADERS PREFIX Libkleo RELATIVE ui ) ecm_generate_pri_file(BASE_NAME Libkleo LIB_NAME KF5Libkleo DEPS "QGpgme" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo ) install(FILES ${libkleo_CamelCase_HEADERS} ${libkleo_CamelCase_models_HEADERS} ${libkleo_CamelCase_ui_HEADERS} ${libkleo_CamelCase_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kleo_export.h ${libkleo_HEADERS} ${libkleo_models_HEADERS} ${libkleo_ui_HEADERS} ${libkleo_utils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/libkleo COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) if ( WIN32 ) install ( FILES libkleopatrarc-win32.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) else () install ( FILES libkleopatrarc.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) endif () diff --git a/src/utils/gnupg.cpp b/src/utils/gnupg.cpp new file mode 100644 index 000000000..b3ab822ad --- /dev/null +++ b/src/utils/gnupg.cpp @@ -0,0 +1,426 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + utils/gnupg.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2008 Klarälvdalens Datakonsult AB + 2016 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + + Kleopatra 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 2 of the License, or + (at your option) any later version. + + Kleopatra 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "gnupg.h" + +#include "utils/hex.h" + +#include +#include +#include + +#include +#include + +#include "libkleo_debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include "gnupg-registry.h" +#endif // Q_OS_WIN + +#include +#include + +using namespace GpgME; + +QString Kleo::gnupgHomeDirectory() +{ +#ifdef Q_OS_WIN + return QFile::decodeName(default_homedir()); +#else + const QByteArray gnupgHome = qgetenv("GNUPGHOME"); + if (!gnupgHome.isEmpty()) { + return QFile::decodeName(gnupgHome); + } else { + return QDir::homePath() + QLatin1String("/.gnupg"); + } +#endif +} + +int Kleo::makeGnuPGError(int code) +{ + return gpg_error(static_cast(code)); +} + +static QString findGpgExe(GpgME::Engine engine, const QString &exe) +{ + const GpgME::EngineInfo info = GpgME::engineInfo(engine); + return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(exe); +} + +QString Kleo::gpgConfPath() +{ + static const auto path = findGpgExe(GpgME::GpgConfEngine, QStringLiteral("gpgconf")); + return path; +} + +QString Kleo::gpgSmPath() +{ + static const auto path = findGpgExe(GpgME::GpgSMEngine, QStringLiteral("gpgsm")); + return path; +} + +QString Kleo::gpgPath() +{ + static const auto path = findGpgExe(GpgME::GpgEngine, QStringLiteral("gpg")); + return path; +} + +QStringList Kleo::gnupgFileWhitelist() +{ + return QStringList() + // The obvious pubring + << QStringLiteral("pubring.gpg") + // GnuPG 2.1 pubring + << QStringLiteral("pubring.kbx") + // Trust in X509 Certificates + << QStringLiteral("trustlist.txt") + // Trustdb controls ownertrust and thus WOT validity + << QStringLiteral("trustdb.gpg") + // We want to update when smartcard status changes + << QStringLiteral("reader*.status") + // No longer used in 2.1 but for 2.0 we want this + << QStringLiteral("secring.gpg") + // Changes to the trustmodel / compliance mode might + // affect validity so we check this, too. + // Globbing for gpg.conf* here will trigger too often + // as gpgconf creates files like gpg.conf.bak or + // gpg.conf.tmp12312.gpgconf that should not trigger + // a change. + << QStringLiteral("gpg.conf") + << QStringLiteral("gpg.conf-?") + << QStringLiteral("gpg.conf-?.?") + ; +} + +QString Kleo::gpg4winVersion() +{ + QFile versionFile(gpg4winInstallPath() + QStringLiteral("/../VERSION")); + if (!versionFile.open(QIODevice::ReadOnly)) { + // No need to translate this should only be the case in development + // builds. + return QStringLiteral("Unknown (no VERSION file found)"); + } + const QString g4wTag = QString::fromUtf8(versionFile.readLine()); + if (!g4wTag.startsWith(QLatin1String("gpg4win"))) { + // Hu? Something unknown + return QStringLiteral("Unknown (invalid VERSION file found)"); + } + // Next line is version. + return QString::fromUtf8(versionFile.readLine()).trimmed(); +} + +QString Kleo::gpg4winInstallPath() +{ +#ifdef Q_OS_WIN + // QApplication::applicationDirPath is only used as a fallback + // to support the case where Kleopatra is not installed from + // Gpg4win but Gpg4win is also installed. + char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", + "Software\\GPG4Win", + "Install Directory"); + if (!instDir) { + // Fallback to HKCU + instDir = read_w32_registry_string("HKEY_CURRENT_USER", + "Software\\GPG4Win", + "Install Directory"); + } + if (instDir) { + QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin"); + free(instDir); + return ret; + } + qCDebug(LIBKLEO_LOG) << "Gpg4win not found. Falling back to Kleopatra instdir."; +#endif + return QCoreApplication::applicationDirPath(); +} + +QString Kleo::gnupgInstallPath() +{ + +#ifdef Q_OS_WIN + // QApplication::applicationDirPath is only used as a fallback + // to support the case where Kleopatra is not installed from + // Gpg4win but Gpg4win is also installed. + char *instDir = read_w32_registry_string("HKEY_LOCAL_MACHINE", + "Software\\GnuPG", + "Install Directory"); + if (!instDir) { + // Fallback to HKCU + instDir = read_w32_registry_string("HKEY_CURRENT_USER", + "Software\\GnuPG", + "Install Directory"); + } + if (instDir) { + QString ret = QString::fromLocal8Bit(instDir) + QStringLiteral("/bin"); + free(instDir); + return ret; + } + qCDebug(LIBKLEO_LOG) << "GnuPG not found. Falling back to gpgconf list dir."; +#endif + return gpgConfListDir("bindir"); +} + +QString Kleo::gpgConfListDir(const char *which) +{ + if (!which || !*which) { + return QString(); + } + const QString gpgConfPath = Kleo::gpgConfPath(); + if (gpgConfPath.isEmpty()) { + return QString(); + } + QProcess gpgConf; + qCDebug(LIBKLEO_LOG) << "gpgConfListDir: starting " << qPrintable(gpgConfPath) << " --list-dirs"; + gpgConf.start(gpgConfPath, QStringList() << QStringLiteral("--list-dirs")); + if (!gpgConf.waitForFinished()) { + qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): failed to execute gpgconf: " << qPrintable(gpgConf.errorString()); + qCDebug(LIBKLEO_LOG) << "output was:\n" << gpgConf.readAllStandardError().constData(); + return QString(); + } + const QList lines = gpgConf.readAllStandardOutput().split('\n'); + for (const QByteArray &line : lines) + if (line.startsWith(which) && line[qstrlen(which)] == ':') { + const int begin = qstrlen(which) + 1; + int end = line.size(); + while (end && (line[end - 1] == '\n' || line[end - 1] == '\r')) { + --end; + } + const QString result = QDir::fromNativeSeparators(QFile::decodeName(hexdecode(line.mid(begin, end - begin)))); + qCDebug(LIBKLEO_LOG) << "gpgConfListDir: found " << qPrintable(result) + << " for '" << which << "'entry"; + return result; + } + qCDebug(LIBKLEO_LOG) << "gpgConfListDir(): didn't find '" << which << "'" + << "entry in output:\n" << gpgConf.readAllStandardError().constData(); + return QString(); +} + +static std::array getVersionFromString(const char *actual, bool &ok) +{ + std::array ret; + ok = false; + + if (!actual) { + return ret; + } + + QString versionString = QString::fromLatin1(actual); + + // Try to fix it up + QRegExp rx(QLatin1String("(\\d+)\\.(\\d+)\\.(\\d+)(?:-svn\\d+)?.*")); + for (int i = 0; i < 3; i++) { + if (!rx.exactMatch(versionString)) { + versionString += QStringLiteral(".0"); + } else { + ok = true; + break; + } + } + + if (!ok) { + qCDebug(LIBKLEO_LOG) << "Can't parse version " << actual; + return ret; + } + + for (int i = 0; i < 3; ++i) { + ret[i] = rx.cap(i + 1).toUInt(&ok); + if (!ok) { + return ret; + } + } + + ok = true; + return ret; +} + +bool Kleo::versionIsAtLeast(const char *minimum, const char *actual) +{ + if (!minimum || !actual) { + return false; + } + bool ok; + const auto minimum_version = getVersionFromString(minimum, ok); + if (!ok) { + return false; + } + const auto actual_version = getVersionFromString(actual, ok); + if (!ok) { + return false; + } + + return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), + std::begin(minimum_version), std::end(minimum_version)); + +} + +bool Kleo::engineIsVersion(int major, int minor, int patch, GpgME::Engine engine) +{ + static QMap > cachedVersions; + const int required_version[] = {major, minor, patch}; + // Gpgconf means spawning processes which is expensive on windows. + std::array actual_version; + if (!cachedVersions.contains(engine)) { + const Error err = checkEngine(engine); + if (err.code() == GPG_ERR_INV_ENGINE) { + qCDebug(LIBKLEO_LOG) << "isVersion: invalid engine. '"; + return false; + } + + const char *actual = GpgME::engineInfo(engine).version(); + bool ok; + actual_version = getVersionFromString(actual, ok); + + qCDebug(LIBKLEO_LOG) << "Parsed" << actual << "as: " + << actual_version[0] << '.' + << actual_version[1] << '.' + << actual_version[2] << '.'; + if (!ok) { + return false; + } + cachedVersions.insert(engine, actual_version); + } else { + actual_version = cachedVersions.value(engine); + } + + // return ! ( actual_version < required_version ) + return !std::lexicographical_compare(std::begin(actual_version), std::end(actual_version), + std::begin(required_version), std::end(required_version)); +} + +const QString& Kleo::paperKeyInstallPath() +{ + static const QString pkPath = QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()).isEmpty() ? + QStandardPaths::findExecutable(QStringLiteral("paperkey")) : + QStandardPaths::findExecutable(QStringLiteral("paperkey"), QStringList() << QCoreApplication::applicationDirPath()); + return pkPath; +} + +bool Kleo::haveKeyserverConfigured() +{ + if (engineIsVersion(2, 1, 19)) { + // since 2.1.19 there is a builtin keyserver + return true; + } + const QGpgME::CryptoConfig *const config = QGpgME::cryptoConfig(); + if (!config) { + return false; + } + const QGpgME::CryptoConfigEntry *const entry = config->entry(QStringLiteral("gpg"), QStringLiteral("Keyserver"), QStringLiteral("keyserver")); + return entry && !entry->stringValue().isEmpty(); +} + +bool Kleo::gpgComplianceP(const char *mode) +{ + const auto conf = QGpgME::cryptoConfig(); + const auto entry = conf->entry(QStringLiteral("gpg"), + QStringLiteral("Configuration"), + QStringLiteral("compliance")); + + return entry && entry->stringValue() == QString::fromLatin1(mode); +} + +enum GpgME::UserID::Validity Kleo::keyValidity(const GpgME::Key &key) +{ + enum UserID::Validity validity = UserID::Validity::Unknown; + + for (const auto &uid: key.userIDs()) { + if (validity == UserID::Validity::Unknown + || validity > uid.validity()) { + validity = uid.validity(); + } + } + + return validity; +} + +#ifdef Q_OS_WIN +static QString fromEncoding (unsigned int src_encoding, const char *data) +{ + int n = MultiByteToWideChar(src_encoding, 0, data, -1, NULL, 0); + if (n < 0) { + return QString(); + } + + wchar_t *result = (wchar_t *) malloc ((n+1) * sizeof *result); + + n = MultiByteToWideChar(src_encoding, 0, data, -1, result, n); + if (n < 0) { + free(result); + return QString(); + } + const auto ret = QString::fromWCharArray(result, n); + free(result); + return ret; +} +#endif + +QString Kleo::stringFromGpgOutput(const QByteArray &ba) +{ +#ifdef Q_OS_WIN + /* Qt on Windows uses GetACP while GnuPG prefers + * GetConsoleOutputCP. + * + * As we are not a console application GetConsoleOutputCP + * usually returns 0. + * From experience the closest thing that let's us guess + * what GetConsoleOutputCP returns for a console application + * it appears to be the OEMCP. + */ + unsigned int cpno = GetConsoleOutputCP (); + if (!cpno) { + cpno = GetOEMCP(); + } + if (!cpno) { + cpno = GetACP(); + } + if (!cpno) { + qCDebug(LIBKLEO_LOG) << "Failed to find native codepage"; + return QString(); + } + + return fromEncoding(cpno, ba.constData()); +#else + return QString::fromLocal8Bit(ba); +#endif +} diff --git a/src/utils/gnupg.h b/src/utils/gnupg.h new file mode 100644 index 000000000..d25990c4b --- /dev/null +++ b/src/utils/gnupg.h @@ -0,0 +1,93 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + utils/gnupg.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2008 Klarälvdalens Datakonsult AB + + Kleopatra 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 2 of the License, or + (at your option) any later version. + + Kleopatra 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KLEOPATRA_GNUPGHELPER_H__ +#define __KLEOPATRA_GNUPGHELPER_H__ + +#include +#include + +/* Support compilation with GPGME older than 1.9. */ +#include +#if GPGMEPP_VERSION > 0x10900 +# define GPGME_HAS_KEY_IS_DEVS +#endif + +/* Does the given object comply with DE_VS? This macro can be used to + ensure that we can still build against older versions of GPGME + without cluttering the code with preprocessor conditionals. */ +#ifdef GPGME_HAS_KEY_IS_DEVS +# define IS_DE_VS(x) (x).isDeVs() +#else +# define IS_DE_VS(x) false +#endif + +#include + +class QString; +class QStringList; +class QByteArray; + +namespace Kleo +{ + +KLEO_EXPORT QString gnupgHomeDirectory(); + +KLEO_EXPORT QString gpgConfPath(); +KLEO_EXPORT QString gpgSmPath(); +KLEO_EXPORT QString gpgPath(); + +KLEO_EXPORT QString gpgConfListDir(const char *which); +KLEO_EXPORT QString gpg4winInstallPath(); +KLEO_EXPORT QString gpg4winVersion(); +KLEO_EXPORT QString gnupgInstallPath(); +KLEO_EXPORT const QString& paperKeyInstallPath(); + +KLEO_EXPORT QStringList gnupgFileWhitelist(); +KLEO_EXPORT int makeGnuPGError(int code); + +KLEO_EXPORT bool engineIsVersion(int major, int minor, int patch, GpgME::Engine = GpgME::GpgConfEngine); +KLEO_EXPORT bool haveKeyserverConfigured(); +KLEO_EXPORT bool gpgComplianceP(const char *mode); +KLEO_EXPORT enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key); + +/* Convert GnuPG output to a QString with proper encoding. + * Takes Gpg Quirks into account and might handle future + * changes in GnuPG Output. */ +KLEO_EXPORT QString stringFromGpgOutput(const QByteArray &ba); + +/* Check if a minimum version is there. Strings should be in the format: + * 1.2.3 */ +KLEO_EXPORT bool versionIsAtLeast(const char *minimum, const char *actual); +} + +#endif // __KLEOPATRA_GNUPGHELPER_H__ diff --git a/src/utils/hex.cpp b/src/utils/hex.cpp new file mode 100644 index 000000000..2d05c2857 --- /dev/null +++ b/src/utils/hex.cpp @@ -0,0 +1,153 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + utils/hex.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2007 Klarälvdalens Datakonsult AB + + Kleopatra 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 2 of the License, or + (at your option) any later version. + + Kleopatra 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "hex.h" + +#include + +#include + +#include +#include + +using namespace Kleo; + +static unsigned char unhex(unsigned char ch) +{ + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } + if (ch >= 'A' && ch <= 'F') { + return ch - 'A' + 10; + } + if (ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } + const char cch = ch; + throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), + i18n("Invalid hex char '%1' in input stream.", + QString::fromLatin1(&cch, 1))); +} + +std::string Kleo::hexdecode(const std::string &in) +{ + std::string result; + result.reserve(in.size()); + for (std::string::const_iterator it = in.begin(), end = in.end(); it != end; ++it) + if (*it == '%') { + ++it; + unsigned char ch = '\0'; + if (it == end) + throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), + i18n("Premature end of hex-encoded char in input stream")); + ch |= unhex(*it) << 4; + ++it; + if (it == end) + throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), + i18n("Premature end of hex-encoded char in input stream")); + ch |= unhex(*it); + result.push_back(ch); + } else if (*it == '+') { + result += ' '; + } else { + result.push_back(*it); + } + return result; +} + +std::string Kleo::hexencode(const std::string &in) +{ + std::string result; + result.reserve(3 * in.size()); + + static const char hex[] = "0123456789ABCDEF"; + + for (std::string::const_iterator it = in.begin(), end = in.end(); it != end; ++it) + switch (const unsigned char ch = *it) { + default: + if ((ch >= '!' && ch <= '~') || ch > 0xA0) { + result += ch; + break; + } + // else fall through + case ' ': + result += '+'; + break; + case '"': + case '#': + case '$': + case '%': + case '\'': + case '+': + case '=': + result += '%'; + result += hex[(ch & 0xF0) >> 4 ]; + result += hex[(ch & 0x0F) ]; + break; + } + + return result; +} + +std::string Kleo::hexdecode(const char *in) +{ + if (!in) { + return std::string(); + } + return hexdecode(std::string(in)); +} + +std::string Kleo::hexencode(const char *in) +{ + if (!in) { + return std::string(); + } + return hexencode(std::string(in)); +} + +QByteArray Kleo::hexdecode(const QByteArray &in) +{ + if (in.isNull()) { + return QByteArray(); + } + const std::string result = hexdecode(std::string(in.constData())); + return QByteArray(result.data(), result.size()); +} + +QByteArray Kleo::hexencode(const QByteArray &in) +{ + if (in.isNull()) { + return QByteArray(); + } + const std::string result = hexencode(std::string(in.constData())); + return QByteArray(result.data(), result.size()); +} diff --git a/src/utils/hex.h b/src/utils/hex.h new file mode 100644 index 000000000..aea14d517 --- /dev/null +++ b/src/utils/hex.h @@ -0,0 +1,54 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + utils/hex.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2007 Klarälvdalens Datakonsult AB + + Kleopatra 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 2 of the License, or + (at your option) any later version. + + Kleopatra 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KLEOPATRA_UTILS_HEX_H__ +#define __KLEOPATRA_UTILS_HEX_H__ + +#include + +class QByteArray; + +namespace Kleo +{ + +std::string hexencode(const char *s); +std::string hexdecode(const char *s); + +std::string hexencode(const std::string &s); +std::string hexdecode(const std::string &s); + +QByteArray hexencode(const QByteArray &s); +QByteArray hexdecode(const QByteArray &s); + +} + +#endif /* __KLEOPATRA_UTILS_HEX_H__ */