Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/kleo/checksumdefinition.cpp b/src/kleo/checksumdefinition.cpp
index 02797bac..e00cbef4 100644
--- a/src/kleo/checksumdefinition.cpp
+++ b/src/kleo/checksumdefinition.cpp
@@ -1,431 +1,431 @@
/* -*- mode: c++; c-basic-offset:4 -*-
checksumdefinition.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "checksumdefinition.h"
#include "kleoexception.h"
-#include "libkleo_debug.h"
+#include <libkleo_debug.h>
#include <KConfig>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KShell>
#include <QByteArray>
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include <QMutex>
#include <QProcess>
#include <QRegularExpression>
#include <KSharedConfig>
#include <QStandardPaths>
#ifdef stdin
#undef stdin // pah..
#endif
using namespace Kleo;
static QMutex installPathMutex;
Q_GLOBAL_STATIC(QString, _installPath)
QString ChecksumDefinition::installPath()
{
const QMutexLocker locker(&installPathMutex);
QString *const ip = _installPath();
if (ip->isEmpty()) {
if (QCoreApplication::instance()) {
*ip = QCoreApplication::applicationDirPath();
} else {
qCWarning(LIBKLEO_LOG) << "checksumdefinition.cpp: installPath() called before QCoreApplication was constructed";
}
}
return *ip;
}
void ChecksumDefinition::setInstallPath(const QString &ip)
{
const QMutexLocker locker(&installPathMutex);
*_installPath() = ip;
}
// Checksum Definition #N groups
static const QLatin1String ID_ENTRY("id");
static const QLatin1String NAME_ENTRY("Name");
static const QLatin1String CREATE_COMMAND_ENTRY("create-command");
static const QLatin1String VERIFY_COMMAND_ENTRY("verify-command");
static const QLatin1String FILE_PATTERNS_ENTRY("file-patterns");
static const QLatin1String OUTPUT_FILE_ENTRY("output-file");
static const QLatin1String FILE_PLACEHOLDER("%f");
static const QLatin1String INSTALLPATH_PLACEHOLDER("%I");
static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR("0|");
static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR('|');
// ChecksumOperations group
static const QLatin1String CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id");
namespace
{
class ChecksumDefinitionError : public Kleo::Exception
{
const QString m_id;
public:
ChecksumDefinitionError(const QString &id, const QString &message)
: Kleo::Exception(GPG_ERR_INV_PARAMETER, i18n("Error in checksum definition %1: %2", id, message), MessageOnly)
, m_id(id)
{
}
~ChecksumDefinitionError() throw() override
{
}
const QString &checksumDefinitionId() const
{
return m_id;
}
};
}
static QString try_extensions(const QString &path)
{
static const char exts[][4] = {
"",
"exe",
"bat",
"bin",
"cmd",
};
static const size_t numExts = sizeof exts / sizeof *exts;
for (unsigned int i = 0; i < numExts; ++i) {
const QFileInfo fi(path + QLatin1Char('.') + QLatin1String(exts[i]));
if (fi.exists()) {
return fi.filePath();
}
}
return QString();
}
static void parse_command(QString cmdline,
const QString &id,
const QString &whichCommand,
QString *command,
QStringList *prefix,
QStringList *suffix,
ChecksumDefinition::ArgumentPassingMethod *method)
{
Q_ASSERT(prefix);
Q_ASSERT(suffix);
Q_ASSERT(method);
KShell::Errors errors;
QStringList l;
if (cmdline.startsWith(NULL_SEPARATED_STDIN_INDICATOR)) {
*method = ChecksumDefinition::NullSeparatedInputFile;
cmdline.remove(0, 2);
} else if (cmdline.startsWith(NEWLINE_SEPARATED_STDIN_INDICATOR)) {
*method = ChecksumDefinition::NewlineSeparatedInputFile;
cmdline.remove(0, 1);
} else {
*method = ChecksumDefinition::CommandLine;
}
if (*method != ChecksumDefinition::CommandLine && cmdline.contains(FILE_PLACEHOLDER)) {
throw ChecksumDefinitionError(id, i18n("Cannot use both %f and | in '%1'", whichCommand));
}
cmdline
.replace(FILE_PLACEHOLDER, QLatin1String("__files_go_here__")) //
.replace(INSTALLPATH_PLACEHOLDER, QStringLiteral("__path_goes_here__"));
l = KShell::splitArgs(cmdline, KShell::AbortOnMeta | KShell::TildeExpand, &errors);
l = l.replaceInStrings(QStringLiteral("__files_go_here__"), FILE_PLACEHOLDER);
static const QRegularExpression regExpression(QLatin1String(".*__path_goes_here__.*"));
if (l.indexOf(regExpression) >= 0) {
l = l.replaceInStrings(QStringLiteral("__path_goes_here__"), ChecksumDefinition::installPath());
}
if (errors == KShell::BadQuoting) {
throw ChecksumDefinitionError(id, i18n("Quoting error in '%1' entry", whichCommand));
}
if (errors == KShell::FoundMeta) {
throw ChecksumDefinitionError(id, i18n("'%1' too complex (would need shell)", whichCommand));
}
qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << l;
if (l.empty()) {
throw ChecksumDefinitionError(id, i18n("'%1' entry is empty/missing", whichCommand));
}
const QFileInfo fi1(l.front());
if (fi1.isAbsolute()) {
*command = try_extensions(l.front());
} else {
*command = QStandardPaths::findExecutable(fi1.fileName());
}
if (command->isEmpty()) {
throw ChecksumDefinitionError(id, i18n("'%1' empty or not found", whichCommand));
}
const int idx1 = l.indexOf(FILE_PLACEHOLDER);
if (idx1 < 0) {
// none -> append
*prefix = l.mid(1);
} else {
*prefix = l.mid(1, idx1 - 1);
*suffix = l.mid(idx1 + 1);
}
switch (*method) {
case ChecksumDefinition::CommandLine:
qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << *command << *prefix << FILE_PLACEHOLDER << *suffix;
break;
case ChecksumDefinition::NewlineSeparatedInputFile:
qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << "find | " << *command << *prefix;
break;
case ChecksumDefinition::NullSeparatedInputFile:
qCDebug(LIBKLEO_LOG) << "ChecksumDefinition[" << id << ']' << "find -print0 | " << *command << *prefix;
break;
case ChecksumDefinition::NumArgumentPassingMethods:
Q_ASSERT(!"Should not happen");
break;
}
}
namespace
{
class KConfigBasedChecksumDefinition : public ChecksumDefinition
{
public:
explicit KConfigBasedChecksumDefinition(const KConfigGroup &group)
: ChecksumDefinition(group.readEntryUntranslated(ID_ENTRY),
group.readEntry(NAME_ENTRY),
group.readEntry(OUTPUT_FILE_ENTRY),
group.readEntry(FILE_PATTERNS_ENTRY, QStringList()))
{
if (id().isEmpty()) {
throw ChecksumDefinitionError(group.name(), i18n("'id' entry is empty/missing"));
}
if (outputFileName().isEmpty()) {
throw ChecksumDefinitionError(id(), i18n("'output-file' entry is empty/missing"));
}
if (patterns().empty()) {
throw ChecksumDefinitionError(id(), i18n("'file-patterns' entry is empty/missing"));
}
// create-command
ArgumentPassingMethod method;
parse_command(group.readEntry(CREATE_COMMAND_ENTRY),
id(),
CREATE_COMMAND_ENTRY,
&m_createCommand,
&m_createPrefixArguments,
&m_createPostfixArguments,
&method);
setCreateCommandArgumentPassingMethod(method);
// verify-command
parse_command(group.readEntry(VERIFY_COMMAND_ENTRY),
id(),
VERIFY_COMMAND_ENTRY,
&m_verifyCommand,
&m_verifyPrefixArguments,
&m_verifyPostfixArguments,
&method);
setVerifyCommandArgumentPassingMethod(method);
}
private:
QString doGetCreateCommand() const override
{
return m_createCommand;
}
QStringList doGetCreateArguments(const QStringList &files) const override
{
return m_createPrefixArguments + files + m_createPostfixArguments;
}
QString doGetVerifyCommand() const override
{
return m_verifyCommand;
}
QStringList doGetVerifyArguments(const QStringList &files) const override
{
return m_verifyPrefixArguments + files + m_verifyPostfixArguments;
}
private:
QString m_createCommand, m_verifyCommand;
QStringList m_createPrefixArguments, m_createPostfixArguments;
QStringList m_verifyPrefixArguments, m_verifyPostfixArguments;
};
}
ChecksumDefinition::ChecksumDefinition(const QString &id, const QString &label, const QString &outputFileName, const QStringList &patterns)
: m_id(id)
, m_label(label.isEmpty() ? id : label)
, m_outputFileName(outputFileName)
, m_patterns(patterns)
, m_createMethod(CommandLine)
, m_verifyMethod(CommandLine)
{
}
ChecksumDefinition::~ChecksumDefinition()
{
}
QString ChecksumDefinition::createCommand() const
{
return doGetCreateCommand();
}
QString ChecksumDefinition::verifyCommand() const
{
return doGetVerifyCommand();
}
#if 0
QStringList ChecksumDefinition::createCommandArguments(const QStringList &files) const
{
return doGetCreateArguments(files);
}
QStringList ChecksumDefinition::verifyCommandArguments(const QStringList &files) const
{
return doGetVerifyArguments(files);
}
#endif
static QByteArray make_input(const QStringList &files, char sep)
{
QByteArray result;
for (const QString &file : files) {
result += QFile::encodeName(file);
result += sep;
}
return result;
}
static bool start_command(QProcess *p,
const char *functionName,
const QString &cmd,
const QStringList &args,
const QStringList &files,
ChecksumDefinition::ArgumentPassingMethod method)
{
if (!p) {
qCWarning(LIBKLEO_LOG) << functionName << ": process == NULL";
return false;
}
switch (method) {
case ChecksumDefinition::NumArgumentPassingMethods:
Q_ASSERT(!"Should not happen");
case ChecksumDefinition::CommandLine:
qCDebug(LIBKLEO_LOG) << "Starting: " << cmd << " " << args.join(QLatin1Char(' '));
p->start(cmd, args, QIODevice::ReadOnly);
return true;
case ChecksumDefinition::NewlineSeparatedInputFile:
case ChecksumDefinition::NullSeparatedInputFile:
qCDebug(LIBKLEO_LOG) << "Starting: " << cmd << " " << args.join(QLatin1Char(' '));
p->start(cmd, args, QIODevice::ReadWrite);
if (!p->waitForStarted()) {
return false;
}
const char sep = method == ChecksumDefinition::NewlineSeparatedInputFile ? '\n' : '\0';
const QByteArray stdin = make_input(files, sep);
if (p->write(stdin) != stdin.size()) {
return false;
}
p->closeWriteChannel();
return true;
}
return false; // make compiler happy
}
bool ChecksumDefinition::startCreateCommand(QProcess *p, const QStringList &files) const
{
return start_command(p,
Q_FUNC_INFO,
doGetCreateCommand(),
m_createMethod == CommandLine ? doGetCreateArguments(files) : doGetCreateArguments(QStringList()),
files,
m_createMethod);
}
bool ChecksumDefinition::startVerifyCommand(QProcess *p, const QStringList &files) const
{
return start_command(p,
Q_FUNC_INFO,
doGetVerifyCommand(),
m_verifyMethod == CommandLine ? doGetVerifyArguments(files) : doGetVerifyArguments(QStringList()),
files,
m_verifyMethod);
}
// static
std::vector<std::shared_ptr<ChecksumDefinition>> ChecksumDefinition::getChecksumDefinitions()
{
QStringList errors;
return getChecksumDefinitions(errors);
}
// static
std::vector<std::shared_ptr<ChecksumDefinition>> ChecksumDefinition::getChecksumDefinitions(QStringList &errors)
{
std::vector<std::shared_ptr<ChecksumDefinition>> result;
KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Checksum Definition #")));
result.reserve(groups.size());
for (const QString &group : groups) {
try {
const std::shared_ptr<ChecksumDefinition> ad(new KConfigBasedChecksumDefinition(KConfigGroup(config, group)));
result.push_back(ad);
} catch (const std::exception &e) {
qDebug() << e.what();
errors.push_back(QString::fromLocal8Bit(e.what()));
} catch (...) {
errors.push_back(i18n("Caught unknown exception in group %1", group));
}
}
return result;
}
// static
std::shared_ptr<ChecksumDefinition>
ChecksumDefinition::getDefaultChecksumDefinition(const std::vector<std::shared_ptr<ChecksumDefinition>> &checksumDefinitions)
{
const KConfigGroup group(KSharedConfig::openConfig(), "ChecksumOperations");
const QString checksumDefinitionId = group.readEntry(CHECKSUM_DEFINITION_ID_ENTRY, QStringLiteral("sha256sum"));
if (!checksumDefinitionId.isEmpty()) {
for (const std::shared_ptr<ChecksumDefinition> &cd : checksumDefinitions) {
if (cd && cd->id() == checksumDefinitionId) {
return cd;
}
}
}
if (!checksumDefinitions.empty()) {
return checksumDefinitions.front();
} else {
return std::shared_ptr<ChecksumDefinition>();
}
}
// static
void ChecksumDefinition::setDefaultChecksumDefinition(const std::shared_ptr<ChecksumDefinition> &checksumDefinition)
{
if (!checksumDefinition) {
return;
}
KConfigGroup group(KSharedConfig::openConfig(), "ChecksumOperations");
group.writeEntry(CHECKSUM_DEFINITION_ID_ENTRY, checksumDefinition->id());
group.sync();
}
diff --git a/src/kleo/docaction.cpp b/src/kleo/docaction.cpp
index c27e1037..4257ea90 100644
--- a/src/kleo/docaction.cpp
+++ b/src/kleo/docaction.cpp
@@ -1,62 +1,60 @@
/*
kleo/docaction.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Andre Heinecke <aheinecke@g10code.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "docaction.h"
-#include "libkleo_debug.h"
+#include <libkleo_debug.h>
#include <QCoreApplication>
#include <QDesktopServices>
#include <QDir>
#include <QFileInfo>
#include <QString>
#include <QUrl>
-#include "libkleo_debug.h"
-
using namespace Kleo;
class Kleo::DocAction::Private
{
public:
explicit Private(const QString &filename, const QString &pathHint);
~Private() = default;
QString path;
bool isEnabled = false;
};
DocAction::Private::Private(const QString &filename, const QString &pathHint)
{
QString tmp = pathHint;
if (!tmp.startsWith(QLatin1Char('/'))) {
tmp.prepend(QLatin1Char('/'));
}
QDir datadir(QCoreApplication::applicationDirPath() + (pathHint.isNull() ? QStringLiteral("/../share/kleopatra") : tmp));
path = datadir.filePath(filename);
QFileInfo fi(path);
isEnabled = fi.exists();
}
DocAction::DocAction(const QIcon &icon, const QString &text, const QString &filename, const QString &pathHint, QObject *parent)
: QAction(icon, text, parent)
, d(new Private(filename, pathHint))
{
setVisible(d->isEnabled);
setEnabled(d->isEnabled);
connect(this, &QAction::triggered, this, [this]() {
if (d->isEnabled) {
qCDebug(LIBKLEO_LOG) << "Opening file:" << d->path;
QDesktopServices::openUrl(QUrl::fromLocalFile(d->path));
}
});
}
DocAction::~DocAction() = default;
diff --git a/src/kleo/enum.cpp b/src/kleo/enum.cpp
index 1cffffde..2c03d420 100644
--- a/src/kleo/enum.cpp
+++ b/src/kleo/enum.cpp
@@ -1,306 +1,308 @@
/*
kleo/enum.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "enum.h"
-#include "libkleo_debug.h"
+
#include "models/keycache.h"
+#include <libkleo_debug.h>
+
#include <functional>
#include <KLazyLocalizedString>
#include <KLocalizedString>
#include <gpgme++/key.h>
#include <gpgme++/tofuinfo.h>
#include <QEventLoop>
#include <QString>
static const struct {
Kleo::CryptoMessageFormat format;
const KLazyLocalizedString displayName;
const char *configName;
} cryptoMessageFormats[] = {
// clang-format off
{Kleo::InlineOpenPGPFormat, kli18n("Inline OpenPGP (deprecated)"), "inline openpgp"},
{Kleo::OpenPGPMIMEFormat, kli18n("OpenPGP/MIME"), "openpgp/mime" },
{Kleo::SMIMEFormat, kli18n("S/MIME"), "s/mime" },
{Kleo::SMIMEOpaqueFormat, kli18n("S/MIME Opaque"), "s/mime opaque" },
{Kleo::AnySMIME, kli18n("Any S/MIME"), "any s/mime" },
{Kleo::AnyOpenPGP, kli18n("Any OpenPGP"), "any openpgp" },
// clang-format on
};
static const unsigned int numCryptoMessageFormats = sizeof cryptoMessageFormats / sizeof *cryptoMessageFormats;
const char *Kleo::cryptoMessageFormatToString(Kleo::CryptoMessageFormat f)
{
if (f == AutoFormat) {
return "auto";
}
for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) {
if (f == cryptoMessageFormats[i].format) {
return cryptoMessageFormats[i].configName;
}
}
return nullptr;
}
QStringList Kleo::cryptoMessageFormatsToStringList(unsigned int f)
{
QStringList result;
for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) {
if (f & cryptoMessageFormats[i].format) {
result.push_back(QLatin1String(cryptoMessageFormats[i].configName));
}
}
return result;
}
QString Kleo::cryptoMessageFormatToLabel(Kleo::CryptoMessageFormat f)
{
if (f == AutoFormat) {
return i18n("Any");
}
for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) {
if (f == cryptoMessageFormats[i].format) {
return KLocalizedString(cryptoMessageFormats[i].displayName).toString();
}
}
return QString();
}
Kleo::CryptoMessageFormat Kleo::stringToCryptoMessageFormat(const QString &s)
{
const QString t = s.toLower();
for (unsigned int i = 0; i < numCryptoMessageFormats; ++i) {
if (t == QLatin1String(cryptoMessageFormats[i].configName)) {
return cryptoMessageFormats[i].format;
}
}
return AutoFormat;
}
unsigned int Kleo::stringListToCryptoMessageFormats(const QStringList &sl)
{
unsigned int result = 0;
for (QStringList::const_iterator it = sl.begin(); it != sl.end(); ++it) {
result |= stringToCryptoMessageFormat(*it);
}
return result;
}
// For the config values used below, see also kaddressbook/editors/cryptowidget.cpp
const char *Kleo::encryptionPreferenceToString(EncryptionPreference pref)
{
switch (pref) {
case UnknownPreference:
return nullptr;
case NeverEncrypt:
return "never";
case AlwaysEncrypt:
return "always";
case AlwaysEncryptIfPossible:
return "alwaysIfPossible";
case AlwaysAskForEncryption:
return "askAlways";
case AskWheneverPossible:
return "askWhenPossible";
}
return nullptr; // keep the compiler happy
}
Kleo::EncryptionPreference Kleo::stringToEncryptionPreference(const QString &str)
{
if (str == QLatin1String("never")) {
return NeverEncrypt;
}
if (str == QLatin1String("always")) {
return AlwaysEncrypt;
}
if (str == QLatin1String("alwaysIfPossible")) {
return AlwaysEncryptIfPossible;
}
if (str == QLatin1String("askAlways")) {
return AlwaysAskForEncryption;
}
if (str == QLatin1String("askWhenPossible")) {
return AskWheneverPossible;
}
return UnknownPreference;
}
QString Kleo::encryptionPreferenceToLabel(EncryptionPreference pref)
{
switch (pref) {
case NeverEncrypt:
return i18n("Never Encrypt");
case AlwaysEncrypt:
return i18n("Always Encrypt");
case AlwaysEncryptIfPossible:
return i18n("Always Encrypt If Possible");
case AlwaysAskForEncryption:
return i18n("Ask");
case AskWheneverPossible:
return i18n("Ask Whenever Possible");
default:
return xi18nc("no specific preference", "<placeholder>none</placeholder>");
}
}
const char *Kleo::signingPreferenceToString(SigningPreference pref)
{
switch (pref) {
case UnknownSigningPreference:
return nullptr;
case NeverSign:
return "never";
case AlwaysSign:
return "always";
case AlwaysSignIfPossible:
return "alwaysIfPossible";
case AlwaysAskForSigning:
return "askAlways";
case AskSigningWheneverPossible:
return "askWhenPossible";
}
return nullptr; // keep the compiler happy
}
Kleo::SigningPreference Kleo::stringToSigningPreference(const QString &str)
{
if (str == QLatin1String("never")) {
return NeverSign;
}
if (str == QLatin1String("always")) {
return AlwaysSign;
}
if (str == QLatin1String("alwaysIfPossible")) {
return AlwaysSignIfPossible;
}
if (str == QLatin1String("askAlways")) {
return AlwaysAskForSigning;
}
if (str == QLatin1String("askWhenPossible")) {
return AskSigningWheneverPossible;
}
return UnknownSigningPreference;
}
QString Kleo::signingPreferenceToLabel(SigningPreference pref)
{
switch (pref) {
case NeverSign:
return i18n("Never Sign");
case AlwaysSign:
return i18n("Always Sign");
case AlwaysSignIfPossible:
return i18n("Always Sign If Possible");
case AlwaysAskForSigning:
return i18n("Ask");
case AskSigningWheneverPossible:
return i18n("Ask Whenever Possible");
default:
return i18nc("no specific preference", "<none>");
}
}
Kleo::TrustLevel Kleo::trustLevel(const GpgME::Key &key)
{
TrustLevel maxTl = Level0;
for (int i = 0, c = key.numUserIDs(); i < c; ++i) {
const auto tl = trustLevel(key.userID(i));
maxTl = qMax(maxTl, tl);
if (maxTl == Level4) {
break;
}
}
return maxTl;
}
namespace
{
bool hasTrustedSignature(const GpgME::UserID &uid)
{
// lazily initialized cache
static std::shared_ptr<const Kleo::KeyCache> keyCache;
if (!keyCache) {
keyCache = Kleo::KeyCache::instance();
}
if (!keyCache->initialized()) {
QEventLoop el;
QObject::connect(keyCache.get(), &Kleo::KeyCache::keyListingDone, &el, &QEventLoop::quit);
el.exec();
}
const auto signatures = uid.signatures();
std::vector<std::string> sigKeyIDs;
std::transform(signatures.cbegin(),
signatures.cend(),
std::back_inserter(sigKeyIDs),
std::bind(&GpgME::UserID::Signature::signerKeyID, std::placeholders::_1));
const auto keys = keyCache->findByKeyIDOrFingerprint(sigKeyIDs);
return std::any_of(keys.cbegin(), keys.cend(), [](const GpgME::Key &key) {
return key.ownerTrust() == GpgME::Key::Ultimate;
});
}
}
Kleo::TrustLevel Kleo::trustLevel(const GpgME::UserID &uid)
{
// Modelled after https://wiki.gnupg.org/EasyGpg2016/AutomatedEncryption,
// but modified to cover all cases, unlike the pseudocode in the document.
//
// TODO: Check whether the key comes from a trusted source (Cert/PKA/DANE/WKD)
switch (uid.validity()) {
case GpgME::UserID::Unknown:
case GpgME::UserID::Undefined:
case GpgME::UserID::Never:
// Not enough trust -> level 0
return Level0;
case GpgME::UserID::Marginal:
// Marginal trust without TOFU data means the key is still trusted
// through the Web of Trust -> level 2
if (uid.tofuInfo().isNull()) {
return Level2;
}
// Marginal trust with TOFU, level will depend on TOFU history
switch (uid.tofuInfo().validity()) {
case GpgME::TofuInfo::ValidityUnknown:
case GpgME::TofuInfo::Conflict:
case GpgME::TofuInfo::NoHistory:
// Marginal trust, but not enough history -> level 0
return Level0;
case GpgME::TofuInfo::LittleHistory:
// Marginal trust, but too little history -> level 1
return Level1;
case GpgME::TofuInfo::BasicHistory:
case GpgME::TofuInfo::LargeHistory:
// Marginal trust and enough history -> level 2
return Level2;
}
return Level2; // Not reached, but avoids fallthrough warnings
case GpgME::UserID::Full:
// Full trust, trust level depends whether the UserID is signed with
// at least one key with Ultimate ownertrust.
return hasTrustedSignature(uid) ? Level4 : Level3;
case GpgME::UserID::Ultimate:
// Ultimate trust -> leve 4
return Level4;
}
Q_UNREACHABLE();
}
diff --git a/src/kleo/keyfiltermanager.cpp b/src/kleo/keyfiltermanager.cpp
index 2af7ced0..1d1e03b0 100644
--- a/src/kleo/keyfiltermanager.cpp
+++ b/src/kleo/keyfiltermanager.cpp
@@ -1,455 +1,456 @@
/*
keyfiltermanager.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keyfiltermanager.h"
#include "defaultkeyfilter.h"
#include "kconfigbasedkeyfilter.h"
#include "stl_util.h"
-#include "libkleo_debug.h"
#include "utils/algorithm.h"
#include "utils/formatting.h"
#include "utils/gnupg.h"
+#include <libkleo_debug.h>
+
#include <KConfig>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QIcon>
#include <QAbstractListModel>
#include <QCoreApplication>
#include <QModelIndex>
#include <QRegularExpression>
#include <QStringList>
#include <algorithm>
#include <climits>
#include <functional>
using namespace Kleo;
using namespace GpgME;
namespace
{
class Model : public QAbstractListModel
{
KeyFilterManager::Private *m_keyFilterManagerPrivate;
public:
explicit Model(KeyFilterManager::Private *p)
: QAbstractListModel(nullptr)
, m_keyFilterManagerPrivate(p)
{
}
int rowCount(const QModelIndex &) const override;
QVariant data(const QModelIndex &idx, int role) const override;
/* upgrade to public */ using QAbstractListModel::beginResetModel;
/* upgrade to public */ using QAbstractListModel::endResetModel;
};
class AllCertificatesKeyFilter : public DefaultKeyFilter
{
public:
AllCertificatesKeyFilter()
: DefaultKeyFilter()
{
setSpecificity(UINT_MAX); // overly high for ordering
setName(i18n("All Certificates"));
setId(QStringLiteral("all-certificates"));
setMatchContexts(Filtering);
}
};
class MyCertificatesKeyFilter : public DefaultKeyFilter
{
public:
MyCertificatesKeyFilter()
: DefaultKeyFilter()
{
setHasSecret(Set);
setSpecificity(UINT_MAX - 1); // overly high for ordering
setName(i18n("My Certificates"));
setId(QStringLiteral("my-certificates"));
setMatchContexts(AnyMatchContext);
setBold(true);
}
};
class TrustedCertificatesKeyFilter : public DefaultKeyFilter
{
public:
TrustedCertificatesKeyFilter()
: DefaultKeyFilter()
{
setRevoked(NotSet);
setValidity(IsAtLeast);
setValidityReferenceLevel(UserID::Marginal);
setSpecificity(UINT_MAX - 2); // overly high for ordering
setName(i18n("Trusted Certificates"));
setId(QStringLiteral("trusted-certificates"));
setMatchContexts(Filtering);
}
};
class FullCertificatesKeyFilter : public DefaultKeyFilter
{
public:
FullCertificatesKeyFilter()
: DefaultKeyFilter()
{
setRevoked(NotSet);
setValidity(IsAtLeast);
setValidityReferenceLevel(UserID::Full);
setSpecificity(UINT_MAX - 3);
setName(i18n("Fully Trusted Certificates"));
setId(QStringLiteral("full-certificates"));
setMatchContexts(Filtering);
}
};
class OtherCertificatesKeyFilter : public DefaultKeyFilter
{
public:
OtherCertificatesKeyFilter()
: DefaultKeyFilter()
{
setHasSecret(NotSet);
setValidity(IsAtMost);
setValidityReferenceLevel(UserID::Never);
setSpecificity(UINT_MAX - 4); // overly high for ordering
setName(i18n("Other Certificates"));
setId(QStringLiteral("other-certificates"));
setMatchContexts(Filtering);
}
};
/* This filter selects uncertified OpenPGP keys, i.e. "good" OpenPGP keys with
* unrevoked user IDs that are not fully valid. */
class UncertifiedOpenPGPKeysFilter : public DefaultKeyFilter
{
public:
UncertifiedOpenPGPKeysFilter()
: DefaultKeyFilter()
{
setSpecificity(UINT_MAX - 6); // overly high for ordering
setName(i18n("Not Certified Certificates"));
setId(QStringLiteral("not-certified-certificates"));
setMatchContexts(Filtering);
setIsOpenPGP(Set);
setIsBad(NotSet);
}
bool matches(const Key &key, MatchContexts contexts) const override
{
return DefaultKeyFilter::matches(key, contexts) && !Formatting::uidsHaveFullValidity(key);
}
};
/* This filter selects only invalid keys (i.e. those where not all
* UIDs are at least fully valid). */
class KeyNotValidFilter : public DefaultKeyFilter
{
public:
KeyNotValidFilter()
: DefaultKeyFilter()
{
setSpecificity(UINT_MAX - 7); // overly high for ordering
setName(i18n("Not Validated Certificates"));
setId(QStringLiteral("not-validated-certificates"));
setMatchContexts(Filtering);
}
bool matches(const Key &key, MatchContexts contexts) const override
{
return DefaultKeyFilter::matches(key, contexts) && !Formatting::uidsHaveFullValidity(key);
}
};
}
static std::vector<std::shared_ptr<KeyFilter>> defaultFilters()
{
std::vector<std::shared_ptr<KeyFilter>> result;
result.reserve(6);
result.push_back(std::shared_ptr<KeyFilter>(new MyCertificatesKeyFilter));
result.push_back(std::shared_ptr<KeyFilter>(new TrustedCertificatesKeyFilter));
result.push_back(std::shared_ptr<KeyFilter>(new FullCertificatesKeyFilter));
result.push_back(std::shared_ptr<KeyFilter>(new OtherCertificatesKeyFilter));
result.push_back(std::shared_ptr<KeyFilter>(new AllCertificatesKeyFilter));
result.push_back(std::shared_ptr<KeyFilter>(new UncertifiedOpenPGPKeysFilter));
result.push_back(std::shared_ptr<KeyFilter>(new KeyNotValidFilter));
return result;
}
class KeyFilterManager::Private
{
public:
Private()
: filters()
, model(this)
{
}
void clear()
{
model.beginResetModel();
filters.clear();
model.endResetModel();
}
std::vector<std::shared_ptr<KeyFilter>> filters;
Model model;
GpgME::Protocol protocol = GpgME::UnknownProtocol;
};
KeyFilterManager *KeyFilterManager::mSelf = nullptr;
KeyFilterManager::KeyFilterManager(QObject *parent)
: QObject(parent)
, d(new Private)
{
mSelf = this;
// ### DF: doesn't a KStaticDeleter work more reliably?
if (QCoreApplication *app = QCoreApplication::instance()) {
connect(app, &QCoreApplication::aboutToQuit, this, &QObject::deleteLater);
}
reload();
}
KeyFilterManager::~KeyFilterManager()
{
mSelf = nullptr;
if (d) {
d->clear();
}
}
KeyFilterManager *KeyFilterManager::instance()
{
if (!mSelf) {
mSelf = new KeyFilterManager();
}
return mSelf;
}
void KeyFilterManager::alwaysFilterByProtocol(GpgME::Protocol protocol)
{
if (protocol != d->protocol) {
d->protocol = protocol;
reload();
}
}
const std::shared_ptr<KeyFilter> &KeyFilterManager::filterMatching(const Key &key, KeyFilter::MatchContexts contexts) const
{
const auto it = std::find_if(d->filters.cbegin(), d->filters.cend(), [&key, contexts](const std::shared_ptr<KeyFilter> &filter) {
return filter->matches(key, contexts);
});
if (it != d->filters.cend()) {
return *it;
}
static const std::shared_ptr<KeyFilter> null;
return null;
}
std::vector<std::shared_ptr<KeyFilter>> KeyFilterManager::filtersMatching(const Key &key, KeyFilter::MatchContexts contexts) const
{
std::vector<std::shared_ptr<KeyFilter>> result;
result.reserve(d->filters.size());
std::remove_copy_if(d->filters.begin(), d->filters.end(), std::back_inserter(result), [&key, contexts](const std::shared_ptr<KeyFilter> &filter) {
return !filter->matches(key, contexts);
});
return result;
}
namespace
{
static const auto byDecreasingSpecificity = [](const std::shared_ptr<KeyFilter> &lhs, const std::shared_ptr<KeyFilter> &rhs) {
return lhs->specificity() > rhs->specificity();
};
}
void KeyFilterManager::reload()
{
d->clear();
d->filters = defaultFilters();
KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
const bool ignoreDeVs = !Kleo::gnupgIsDeVsCompliant();
for (QStringList::const_iterator it = groups.begin(); it != groups.end(); ++it) {
const KConfigGroup cfg(config, *it);
if (cfg.hasKey("is-de-vs") && ignoreDeVs) {
/* Don't show de-vs filters in other compliance modes */
continue;
}
d->filters.push_back(std::shared_ptr<KeyFilter>(new KConfigBasedKeyFilter(cfg)));
}
std::stable_sort(d->filters.begin(), d->filters.end(), byDecreasingSpecificity);
if (d->protocol != GpgME::UnknownProtocol) {
// remove filters with conflicting isOpenPGP rule
const auto conflictingValue = (d->protocol == GpgME::OpenPGP) ? DefaultKeyFilter::NotSet : DefaultKeyFilter::Set;
Kleo::erase_if(d->filters, [conflictingValue](const auto &f) {
const auto filter = std::dynamic_pointer_cast<DefaultKeyFilter>(f);
Q_ASSERT(filter);
return filter->isOpenPGP() == conflictingValue;
});
// add isOpenPGP rule to all filters
const auto isOpenPGPValue = (d->protocol == GpgME::OpenPGP) ? DefaultKeyFilter::Set : DefaultKeyFilter::NotSet;
std::for_each(std::begin(d->filters), std::end(d->filters), [isOpenPGPValue](auto &f) {
const auto filter = std::dynamic_pointer_cast<DefaultKeyFilter>(f);
Q_ASSERT(filter);
return filter->setIsOpenPGP(isOpenPGPValue);
});
}
qCDebug(LIBKLEO_LOG) << "KeyFilterManager::" << __func__ << "final filter count is" << d->filters.size();
}
QAbstractItemModel *KeyFilterManager::model() const
{
return &d->model;
}
const std::shared_ptr<KeyFilter> &KeyFilterManager::keyFilterByID(const QString &id) const
{
const auto it = std::find_if(d->filters.begin(), d->filters.end(), [id](const std::shared_ptr<KeyFilter> &filter) {
return filter->id() == id;
});
if (it != d->filters.end()) {
return *it;
}
static const std::shared_ptr<KeyFilter> null;
return null;
}
const std::shared_ptr<KeyFilter> &KeyFilterManager::fromModelIndex(const QModelIndex &idx) const
{
if (!idx.isValid() || idx.model() != &d->model || idx.row() < 0 || static_cast<unsigned>(idx.row()) >= d->filters.size()) {
static const std::shared_ptr<KeyFilter> null;
return null;
}
return d->filters[idx.row()];
}
QModelIndex KeyFilterManager::toModelIndex(const std::shared_ptr<KeyFilter> &kf) const
{
if (!kf) {
return {};
}
const auto pair = std::equal_range(d->filters.cbegin(), d->filters.cend(), kf, byDecreasingSpecificity);
const auto it = std::find(pair.first, pair.second, kf);
if (it != pair.second) {
return d->model.index(it - d->filters.begin());
} else {
return QModelIndex();
}
}
int Model::rowCount(const QModelIndex &) const
{
return m_keyFilterManagerPrivate->filters.size();
}
QVariant Model::data(const QModelIndex &idx, int role) const
{
if (!idx.isValid() || idx.model() != this || idx.row() < 0 || static_cast<unsigned>(idx.row()) > m_keyFilterManagerPrivate->filters.size()) {
return QVariant();
}
const auto filter = m_keyFilterManagerPrivate->filters[idx.row()];
switch (role) {
case Qt::DecorationRole:
return filter->icon();
case Qt::DisplayRole:
case Qt::EditRole:
case Qt::ToolTipRole: /* Most useless tooltip ever. */
return filter->name();
case KeyFilterManager::FilterIdRole:
return filter->id();
case KeyFilterManager::FilterMatchContextsRole:
return QVariant::fromValue(filter->availableMatchContexts());
default:
return QVariant();
}
}
static KeyFilter::FontDescription
get_fontdescription(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, const KeyFilter::FontDescription &initial)
{
return kdtools::accumulate_if(
filters.begin(),
filters.end(),
[&key](const std::shared_ptr<KeyFilter> &filter) {
return filter->matches(key, KeyFilter::Appearance);
},
initial,
[](const KeyFilter::FontDescription &lhs, const std::shared_ptr<KeyFilter> &rhs) {
return lhs.resolve(rhs->fontDescription());
});
}
QFont KeyFilterManager::font(const Key &key, const QFont &baseFont) const
{
const KeyFilter::FontDescription fd = get_fontdescription(d->filters, key, KeyFilter::FontDescription());
return fd.font(baseFont);
}
static QColor get_color(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, QColor (KeyFilter::*fun)() const)
{
const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr<KeyFilter> &filter) {
return filter->matches(key, KeyFilter::Appearance) && (filter.get()->*fun)().isValid();
});
if (it == filters.cend()) {
return {};
} else {
return (it->get()->*fun)();
}
}
static QString get_string(const std::vector<std::shared_ptr<KeyFilter>> &filters, const Key &key, QString (KeyFilter::*fun)() const)
{
const auto it = std::find_if(filters.cbegin(), filters.cend(), [&fun, &key](const std::shared_ptr<KeyFilter> &filter) {
return filter->matches(key, KeyFilter::Appearance) && !(filter.get()->*fun)().isEmpty();
});
if (it == filters.cend()) {
return QString();
} else {
return (*it)->icon();
}
}
QColor KeyFilterManager::bgColor(const Key &key) const
{
return get_color(d->filters, key, &KeyFilter::bgColor);
}
QColor KeyFilterManager::fgColor(const Key &key) const
{
return get_color(d->filters, key, &KeyFilter::fgColor);
}
QIcon KeyFilterManager::icon(const Key &key) const
{
const QString icon = get_string(d->filters, key, &KeyFilter::icon);
return icon.isEmpty() ? QIcon() : QIcon::fromTheme(icon);
}
diff --git a/src/kleo/keygroupconfig.cpp b/src/kleo/keygroupconfig.cpp
index 1cb74d12..f0ee1c61 100644
--- a/src/kleo/keygroupconfig.cpp
+++ b/src/kleo/keygroupconfig.cpp
@@ -1,175 +1,175 @@
/*
kleo/keygroupconfig.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keygroupconfig.h"
#include "debug.h"
#include "keygroup.h"
#include "models/keycache.h"
#include "utils/keyhelpers.h"
#include "utils/qtstlhelpers.h"
+#include <libkleo_debug.h>
+
#include <KConfigGroup>
#include <KSharedConfig>
#include <QString>
#include <gpgme++/key.h>
-#include "libkleo_debug.h"
-
using namespace Kleo;
using namespace GpgME;
static const QString groupNamePrefix = QStringLiteral("Group-");
class KeyGroupConfig::Private
{
public:
explicit Private(const QString &filename);
std::vector<KeyGroup> readGroups() const;
KeyGroup writeGroup(const KeyGroup &group);
bool removeGroup(const KeyGroup &group);
private:
KeyGroup readGroup(const KSharedConfigPtr &groupsConfig, const QString &groupId) const;
private:
QString filename;
};
KeyGroupConfig::Private::Private(const QString &filename)
: filename{filename}
{
if (filename.isEmpty()) {
qCWarning(LIBKLEO_LOG) << __func__ << "Warning: name of configuration file is empty";
}
}
KeyGroup KeyGroupConfig::Private::readGroup(const KSharedConfigPtr &groupsConfig, const QString &groupId) const
{
const KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + groupId);
const QString groupName = configGroup.readEntry("Name", QString());
const auto fingerprints = toStdStrings(configGroup.readEntry("Keys", QStringList()));
const std::vector<Key> groupKeys = KeyCache::instance()->findByFingerprint(fingerprints);
// treat group as immutable if any of its entries is immutable
const QStringList entries = configGroup.keyList();
const bool isImmutable = (configGroup.isImmutable() //
|| std::any_of(entries.begin(), entries.end(), [configGroup](const QString &entry) {
return configGroup.isEntryImmutable(entry);
}));
KeyGroup g(groupId, groupName, groupKeys, KeyGroup::ApplicationConfig);
g.setIsImmutable(isImmutable);
qCDebug(LIBKLEO_LOG) << "Read group" << g;
return g;
}
std::vector<KeyGroup> KeyGroupConfig::Private::readGroups() const
{
std::vector<KeyGroup> groups;
if (filename.isEmpty()) {
return groups;
}
const KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename);
const QStringList configGroups = groupsConfig->groupList();
for (const QString &configGroupName : configGroups) {
qCDebug(LIBKLEO_LOG) << "Reading config group" << configGroupName;
if (configGroupName.startsWith(groupNamePrefix)) {
const QString keyGroupId = configGroupName.mid(groupNamePrefix.size());
if (keyGroupId.isEmpty()) {
qCWarning(LIBKLEO_LOG) << "Config group" << configGroupName << "has empty group id";
continue;
}
KeyGroup group = readGroup(groupsConfig, keyGroupId);
groups.push_back(group);
}
}
return groups;
}
KeyGroup KeyGroupConfig::Private::writeGroup(const KeyGroup &group)
{
if (filename.isEmpty()) {
return {};
}
if (group.isNull()) {
qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null";
return group;
}
KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename);
KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + group.id());
qCDebug(LIBKLEO_LOG) << __func__ << "Writing config group" << configGroup.name();
configGroup.writeEntry("Name", group.name());
configGroup.writeEntry("Keys", Kleo::getFingerprints(group.keys()));
// reread group to ensure that it reflects the saved group in case of immutable entries
return readGroup(groupsConfig, group.id());
}
bool KeyGroupConfig::Private::removeGroup(const KeyGroup &group)
{
if (filename.isEmpty()) {
return false;
}
if (group.isNull()) {
qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null";
return false;
}
KSharedConfigPtr groupsConfig = KSharedConfig::openConfig(filename);
KConfigGroup configGroup = groupsConfig->group(groupNamePrefix + group.id());
qCDebug(LIBKLEO_LOG) << __func__ << "Removing config group" << configGroup.name();
configGroup.deleteGroup();
return true;
}
KeyGroupConfig::KeyGroupConfig(const QString &filename)
: d{std::make_unique<Private>(filename)}
{
}
KeyGroupConfig::~KeyGroupConfig() = default;
std::vector<KeyGroup> KeyGroupConfig::readGroups() const
{
return d->readGroups();
}
KeyGroup KeyGroupConfig::writeGroup(const KeyGroup &group)
{
return d->writeGroup(group);
}
void KeyGroupConfig::writeGroups(const std::vector<KeyGroup> &groups)
{
std::for_each(std::begin(groups), std::end(groups), [this](const auto &group) {
d->writeGroup(group);
});
}
bool KeyGroupConfig::removeGroup(const KeyGroup &group)
{
return d->removeGroup(group);
}
diff --git a/src/kleo/keygroupimportexport.cpp b/src/kleo/keygroupimportexport.cpp
index 4af16eca..f67b2752 100644
--- a/src/kleo/keygroupimportexport.cpp
+++ b/src/kleo/keygroupimportexport.cpp
@@ -1,149 +1,149 @@
/*
kleo/keygroupimportexport.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "keygroupimportexport.h"
#include "debug.h"
#include "keygroup.h"
#include "models/keycache.h"
#include "utils/keyhelpers.h"
#include "utils/qtstlhelpers.h"
+#include <libkleo_debug.h>
+
#include <QFile>
#include <QSettings>
#include <QString>
-#include "libkleo_debug.h"
-
using namespace Kleo;
using namespace GpgME;
// use a different, less generic prefix for the config group names than in
// KeyGroupConfig to avoid problems with "Group-*" config groups created by
// other applications; this means that the key groups stored in the normal group
// configuration file cannot be read with the below functions, but that's a good
// thing because the ini files created by KConfig are incompatible with QSettings
static const QString keyGroupNamePrefix = QStringLiteral("KeyGroup-");
namespace
{
QString readString(const QSettings &settings, const QString &key)
{
return settings.value(key, QString{}).toString();
}
QStringList readStringList(const QSettings &settings, const QString &key)
{
auto variant = settings.value(key);
if (!variant.isValid()) {
return {};
}
if ((variant.userType() == QMetaType::QString) && variant.toString().isEmpty()) {
// interpret empty string value as empty list instead of as list with an empty string
return {};
}
// opportunistically, interpret the value as string list
return variant.toStringList();
}
void writeString(QSettings &settings, const QString &key, const QString &string)
{
settings.setValue(key, string);
}
void writeStringList(QSettings &settings, const QString &key, const QStringList &list)
{
// write empty list as empty string to avoid Qt's "@Invalid()"
if (list.empty()) {
writeString(settings, key, {});
} else {
settings.setValue(key, list);
}
}
KeyGroup readGroup(const QSettings &groupsConfig, const QString &groupId)
{
const auto configGroupPath = keyGroupNamePrefix + groupId + QLatin1Char{'/'};
const auto groupName = readString(groupsConfig, configGroupPath + QLatin1String{"Name"});
const auto fingerprints = readStringList(groupsConfig, configGroupPath + QLatin1String{"Keys"});
const std::vector<Key> groupKeys = KeyCache::instance()->findByFingerprint(toStdStrings(fingerprints));
KeyGroup g(groupId, groupName, groupKeys, KeyGroup::ApplicationConfig);
qCDebug(LIBKLEO_LOG) << __func__ << "Read group" << g;
return g;
}
void writeGroup(QSettings &groupsConfig, const KeyGroup &group)
{
if (group.isNull()) {
qCDebug(LIBKLEO_LOG) << __func__ << "Error: group is null";
return;
}
const auto configGroupName = keyGroupNamePrefix + group.id();
qCDebug(LIBKLEO_LOG) << __func__ << "Writing config group" << configGroupName;
const auto configGroupPath = configGroupName + QLatin1Char{'/'};
writeString(groupsConfig, configGroupPath + QLatin1String{"Name"}, group.name());
writeStringList(groupsConfig, configGroupPath + QLatin1String{"Keys"}, Kleo::getFingerprints(group.keys()));
}
} // namespace
std::vector<KeyGroup> Kleo::readKeyGroups(const QString &filename)
{
std::vector<KeyGroup> groups;
if (filename.isEmpty()) {
return groups;
}
if (!QFile::exists(filename)) {
qCWarning(LIBKLEO_LOG) << __func__ << "File" << filename << "does not exist";
return groups;
}
const QSettings groupsConfig{filename, QSettings::IniFormat};
const QStringList configGroups = groupsConfig.childGroups();
for (const QString &configGroupName : configGroups) {
if (configGroupName.startsWith(keyGroupNamePrefix)) {
qCDebug(LIBKLEO_LOG) << __func__ << "Reading config group" << configGroupName;
const QString keyGroupId = configGroupName.mid(keyGroupNamePrefix.size());
if (keyGroupId.isEmpty()) {
qCWarning(LIBKLEO_LOG) << __func__ << "Config group" << configGroupName << "has empty group id";
continue;
}
groups.push_back(readGroup(groupsConfig, keyGroupId));
}
}
return groups;
}
Kleo::WriteKeyGroups Kleo::writeKeyGroups(const QString &filename, const std::vector<KeyGroup> &groups)
{
if (filename.isEmpty()) {
return WriteKeyGroups::InvalidFilename;
}
QSettings groupsConfig{filename, QSettings::IniFormat};
for (const auto &group : groups) {
writeGroup(groupsConfig, group);
}
// ensure that the data is written to disk before calling status()
groupsConfig.sync();
qCDebug(LIBKLEO_LOG) << __func__ << "groupsConfig.status():" << groupsConfig.status();
return groupsConfig.status() == QSettings::NoError ? WriteKeyGroups::Success : WriteKeyGroups::Error;
}
diff --git a/src/kleo/keyresolver.cpp b/src/kleo/keyresolver.cpp
index baf0493c..534aab43 100644
--- a/src/kleo/keyresolver.cpp
+++ b/src/kleo/keyresolver.cpp
@@ -1,162 +1,162 @@
/* -*- c++ -*-
keyresolver.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
Based on kpgp.cpp
SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
See file libkdenetwork/AUTHORS.kpgp for details
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keyresolver.h"
#include "keyresolvercore.h"
#include "models/keycache.h"
#include "ui/newkeyapprovaldialog.h"
#include "utils/formatting.h"
-#include <gpgme++/key.h>
+#include <libkleo_debug.h>
-#include "libkleo_debug.h"
+#include <gpgme++/key.h>
using namespace Kleo;
using namespace GpgME;
class KeyResolver::Private
{
public:
Private(KeyResolver *qq, bool enc, bool sig, Protocol fmt, bool allowMixed)
: q(qq)
, mCore(enc, sig, fmt)
, mFormat(fmt)
, mEncrypt(enc)
, mSign(sig)
, mAllowMixed(allowMixed)
, mCache(KeyCache::instance())
, mDialogWindowFlags(Qt::WindowFlags())
, mPreferredProtocol(UnknownProtocol)
{
mCore.setAllowMixedProtocols(allowMixed);
}
~Private() = default;
void showApprovalDialog(KeyResolverCore::Result result, QWidget *parent);
void dialogAccepted();
KeyResolver *const q;
KeyResolverCore mCore;
Solution mResult;
Protocol mFormat;
bool mEncrypt;
bool mSign;
bool mAllowMixed;
// The cache is needed as a member variable to avoid rebuilding
// it between calls if we are the only user.
std::shared_ptr<const KeyCache> mCache;
std::unique_ptr<NewKeyApprovalDialog> mDialog;
Qt::WindowFlags mDialogWindowFlags;
Protocol mPreferredProtocol;
};
void KeyResolver::Private::showApprovalDialog(KeyResolverCore::Result result, QWidget *parent)
{
const QString sender = mCore.normalizedSender();
mDialog = std::make_unique<NewKeyApprovalDialog>(mEncrypt,
mSign,
sender,
std::move(result.solution),
std::move(result.alternative),
mAllowMixed,
mFormat,
parent,
mDialogWindowFlags);
connect(mDialog.get(), &QDialog::accepted, q, [this]() {
dialogAccepted();
});
connect(mDialog.get(), &QDialog::rejected, q, [this]() {
Q_EMIT q->keysResolved(false, false);
});
mDialog->open();
}
void KeyResolver::Private::dialogAccepted()
{
mResult = mDialog->result();
Q_EMIT q->keysResolved(true, false);
}
void KeyResolver::start(bool showApproval, QWidget *parentWidget)
{
qCDebug(LIBKLEO_LOG) << "Starting ";
if (!d->mSign && !d->mEncrypt) {
// nothing to do
return Q_EMIT keysResolved(true, true);
}
const auto result = d->mCore.resolve();
const bool success = (result.flags & KeyResolverCore::AllResolved);
if (success && !showApproval) {
d->mResult = std::move(result.solution);
Q_EMIT keysResolved(true, false);
return;
} else if (success) {
qCDebug(LIBKLEO_LOG) << "No need for the user showing approval anyway.";
}
d->showApprovalDialog(std::move(result), parentWidget);
}
KeyResolver::KeyResolver(bool encrypt, bool sign, Protocol fmt, bool allowMixed)
: d(new Private(this, encrypt, sign, fmt, allowMixed))
{
}
Kleo::KeyResolver::~KeyResolver() = default;
void KeyResolver::setRecipients(const QStringList &addresses)
{
d->mCore.setRecipients(addresses);
}
void KeyResolver::setSender(const QString &address)
{
d->mCore.setSender(address);
}
void KeyResolver::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides)
{
d->mCore.setOverrideKeys(overrides);
}
void KeyResolver::setSigningKeys(const QStringList &fingerprints)
{
d->mCore.setSigningKeys(fingerprints);
}
KeyResolver::Solution KeyResolver::result() const
{
return d->mResult;
}
void KeyResolver::setDialogWindowFlags(Qt::WindowFlags flags)
{
d->mDialogWindowFlags = flags;
}
void KeyResolver::setPreferredProtocol(Protocol proto)
{
d->mCore.setPreferredProtocol(proto);
}
void KeyResolver::setMinimumValidity(int validity)
{
d->mCore.setMinimumValidity(validity);
}
diff --git a/src/kleo/keyresolvercore.cpp b/src/kleo/keyresolvercore.cpp
index e698273f..70c13719 100644
--- a/src/kleo/keyresolvercore.cpp
+++ b/src/kleo/keyresolvercore.cpp
@@ -1,785 +1,785 @@
/* -*- c++ -*-
kleo/keyresolvercore.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
Based on kpgp.cpp
SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
See file libkdenetwork/AUTHORS.kpgp for details
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keyresolvercore.h"
#include "kleo/enum.h"
#include "kleo/keygroup.h"
#include "models/keycache.h"
#include "utils/formatting.h"
#include "utils/gnupg.h"
-#include <gpgme++/key.h>
+#include <libkleo_debug.h>
-#include "libkleo_debug.h"
+#include <gpgme++/key.h>
using namespace Kleo;
using namespace GpgME;
namespace
{
QDebug operator<<(QDebug debug, const GpgME::Key &key)
{
if (key.isNull()) {
debug << "Null";
} else {
debug << Formatting::summaryLine(key);
}
return debug.maybeSpace();
}
static inline bool ValidEncryptionKey(const Key &key)
{
if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt()) {
return false;
}
return true;
}
static inline bool ValidSigningKey(const Key &key)
{
if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() || !key.hasSecret()) {
return false;
}
return true;
}
static int keyValidity(const Key &key, const QString &address)
{
// returns the validity of the UID matching the address or, if no UID matches, the maximal validity of all UIDs
int overallValidity = UserID::Validity::Unknown;
for (const auto &uid : key.userIDs()) {
if (QString::fromStdString(uid.addrSpec()).toLower() == address.toLower()) {
return uid.validity();
}
overallValidity = std::max(overallValidity, static_cast<int>(uid.validity()));
}
return overallValidity;
}
static int minimumValidity(const std::vector<Key> &keys, const QString &address)
{
const int minValidity = std::accumulate(keys.cbegin(), //
keys.cend(),
UserID::Ultimate + 1,
[address](int validity, const Key &key) {
return std::min<int>(validity, keyValidity(key, address));
});
return minValidity <= UserID::Ultimate ? static_cast<UserID::Validity>(minValidity) : UserID::Unknown;
}
bool allKeysHaveProtocol(const std::vector<Key> &keys, Protocol protocol)
{
return std::all_of(keys.cbegin(), keys.cend(), [protocol](const Key &key) {
return key.protocol() == protocol;
});
}
bool anyKeyHasProtocol(const std::vector<Key> &keys, Protocol protocol)
{
return std::any_of(std::begin(keys), std::end(keys), [protocol](const Key &key) {
return key.protocol() == protocol;
});
}
} // namespace
class KeyResolverCore::Private
{
public:
Private(KeyResolverCore *qq, bool enc, bool sig, Protocol fmt)
: q(qq)
, mFormat(fmt)
, mEncrypt(enc)
, mSign(sig)
, mCache(KeyCache::instance())
, mPreferredProtocol(UnknownProtocol)
, mMinimumValidity(UserID::Marginal)
{
}
~Private() = default;
bool isAcceptableSigningKey(const Key &key);
bool isAcceptableEncryptionKey(const Key &key, const QString &address = QString());
void setSender(const QString &address);
void addRecipients(const QStringList &addresses);
void setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides);
void resolveOverrides();
std::vector<Key> resolveRecipientWithGroup(const QString &address, Protocol protocol);
void resolveEncryptionGroups();
std::vector<Key> resolveSenderWithGroup(const QString &address, Protocol protocol);
void resolveSigningGroups();
void resolveSign(Protocol proto);
void setSigningKeys(const QStringList &fingerprints);
std::vector<Key> resolveRecipient(const QString &address, Protocol protocol);
void resolveEnc(Protocol proto);
void mergeEncryptionKeys();
Result resolve();
KeyResolverCore *const q;
QString mSender;
QStringList mRecipients;
QMap<Protocol, std::vector<Key>> mSigKeys;
QMap<QString, QMap<Protocol, std::vector<Key>>> mEncKeys;
QMap<QString, QMap<Protocol, QStringList>> mOverrides;
Protocol mFormat;
QStringList mFatalErrors;
bool mEncrypt;
bool mSign;
// The cache is needed as a member variable to avoid rebuilding
// it between calls if we are the only user.
std::shared_ptr<const KeyCache> mCache;
bool mAllowMixed = true;
Protocol mPreferredProtocol;
int mMinimumValidity;
};
bool KeyResolverCore::Private::isAcceptableSigningKey(const Key &key)
{
if (!ValidSigningKey(key)) {
return false;
}
if (Kleo::gnupgIsDeVsCompliant()) {
if (!Formatting::isKeyDeVs(key)) {
qCDebug(LIBKLEO_LOG) << "Rejected sig key" << key.primaryFingerprint() << "because it is not de-vs compliant.";
return false;
}
}
return true;
}
bool KeyResolverCore::Private::isAcceptableEncryptionKey(const Key &key, const QString &address)
{
if (!ValidEncryptionKey(key)) {
return false;
}
if (Kleo::gnupgIsDeVsCompliant()) {
if (!Formatting::isKeyDeVs(key)) {
qCDebug(LIBKLEO_LOG) << "Rejected enc key" << key.primaryFingerprint() << "because it is not de-vs compliant.";
return false;
}
}
if (address.isEmpty()) {
return true;
}
for (const auto &uid : key.userIDs()) {
if (uid.addrSpec() == address.toStdString()) {
if (uid.validity() >= mMinimumValidity) {
return true;
}
}
}
return false;
}
void KeyResolverCore::Private::setSender(const QString &address)
{
const auto normalized = UserID::addrSpecFromString(address.toUtf8().constData());
if (normalized.empty()) {
// should not happen bug in the caller, non localized
// error for bug reporting.
mFatalErrors << QStringLiteral("The sender address '%1' could not be extracted").arg(address);
return;
}
const auto normStr = QString::fromUtf8(normalized.c_str());
mSender = normStr;
addRecipients({address});
}
void KeyResolverCore::Private::addRecipients(const QStringList &addresses)
{
if (!mEncrypt) {
return;
}
// Internally we work with normalized addresses. Normalization
// matches the gnupg one.
for (const auto &addr : addresses) {
// PGP Uids are defined to be UTF-8 (RFC 4880 §5.11)
const auto normalized = UserID::addrSpecFromString(addr.toUtf8().constData());
if (normalized.empty()) {
// should not happen bug in the caller, non localized
// error for bug reporting.
mFatalErrors << QStringLiteral("The mail address for '%1' could not be extracted").arg(addr);
continue;
}
const QString normStr = QString::fromUtf8(normalized.c_str());
mRecipients << normStr;
// Initially add empty lists of keys for both protocols
mEncKeys[normStr] = {{CMS, {}}, {OpenPGP, {}}};
}
}
void KeyResolverCore::Private::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides)
{
for (auto protocolIt = overrides.cbegin(); protocolIt != overrides.cend(); ++protocolIt) {
const Protocol &protocol = protocolIt.key();
const auto &addressFingerprintMap = protocolIt.value();
for (auto addressIt = addressFingerprintMap.cbegin(); addressIt != addressFingerprintMap.cend(); ++addressIt) {
const QString &address = addressIt.key();
const QStringList &fingerprints = addressIt.value();
const QString normalizedAddress = QString::fromUtf8(UserID::addrSpecFromString(address.toUtf8().constData()).c_str());
mOverrides[normalizedAddress][protocol] = fingerprints;
}
}
}
namespace
{
std::vector<Key> resolveOverride(const QString &address, Protocol protocol, const QStringList &fingerprints)
{
std::vector<Key> keys;
for (const auto &fprOrId : fingerprints) {
const Key key = KeyCache::instance()->findByKeyIDOrFingerprint(fprOrId.toUtf8().constData());
if (key.isNull()) {
// FIXME: Report to caller
qCDebug(LIBKLEO_LOG) << "Failed to find override key for:" << address << "fpr:" << fprOrId;
continue;
}
if (protocol != UnknownProtocol && key.protocol() != protocol) {
qCDebug(LIBKLEO_LOG) << "Ignoring key" << Formatting::summaryLine(key) << "given as" << Formatting::displayName(protocol) << "override for"
<< address;
continue;
}
qCDebug(LIBKLEO_LOG) << "Using key" << Formatting::summaryLine(key) << "as" << Formatting::displayName(protocol) << "override for" << address;
keys.push_back(key);
}
return keys;
}
}
void KeyResolverCore::Private::resolveOverrides()
{
if (!mEncrypt) {
// No encryption we are done.
return;
}
for (auto addressIt = mOverrides.cbegin(); addressIt != mOverrides.cend(); ++addressIt) {
const QString &address = addressIt.key();
const auto &protocolFingerprintsMap = addressIt.value();
if (!mRecipients.contains(address)) {
qCDebug(LIBKLEO_LOG) << "Overrides provided for an address that is "
"neither sender nor recipient. Address:"
<< address;
continue;
}
const QStringList commonOverride = protocolFingerprintsMap.value(UnknownProtocol);
if (!commonOverride.empty()) {
mEncKeys[address][UnknownProtocol] = resolveOverride(address, UnknownProtocol, commonOverride);
if (protocolFingerprintsMap.contains(OpenPGP)) {
qCDebug(LIBKLEO_LOG) << "Ignoring OpenPGP-specific override for" << address << "in favor of common override";
}
if (protocolFingerprintsMap.contains(CMS)) {
qCDebug(LIBKLEO_LOG) << "Ignoring S/MIME-specific override for" << address << "in favor of common override";
}
} else {
if (mFormat != CMS) {
mEncKeys[address][OpenPGP] = resolveOverride(address, OpenPGP, protocolFingerprintsMap.value(OpenPGP));
}
if (mFormat != OpenPGP) {
mEncKeys[address][CMS] = resolveOverride(address, CMS, protocolFingerprintsMap.value(CMS));
}
}
}
}
std::vector<Key> KeyResolverCore::Private::resolveSenderWithGroup(const QString &address, Protocol protocol)
{
// prefer single-protocol groups over mixed-protocol groups
auto group = mCache->findGroup(address, protocol, KeyCache::KeyUsage::Sign);
if (group.isNull()) {
group = mCache->findGroup(address, UnknownProtocol, KeyCache::KeyUsage::Sign);
}
if (group.isNull()) {
return {};
}
// take the first key matching the protocol
const auto &keys = group.keys();
const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) {
return key.protocol() == protocol;
});
if (it == std::end(keys)) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has no" << Formatting::displayName(protocol) << "signing key";
return {};
}
const auto key = *it;
if (!isAcceptableSigningKey(key)) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has unacceptable signing key" << key;
return {};
}
return {key};
}
void KeyResolverCore::Private::resolveSigningGroups()
{
auto &protocolKeysMap = mSigKeys;
if (!protocolKeysMap[UnknownProtocol].empty()) {
// already resolved by common override
return;
}
if (mFormat == OpenPGP) {
if (!protocolKeysMap[OpenPGP].empty()) {
// already resolved by override
return;
}
protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP);
} else if (mFormat == CMS) {
if (!protocolKeysMap[CMS].empty()) {
// already resolved by override
return;
}
protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS);
} else {
protocolKeysMap[OpenPGP] = resolveSenderWithGroup(mSender, OpenPGP);
protocolKeysMap[CMS] = resolveSenderWithGroup(mSender, CMS);
}
}
void KeyResolverCore::Private::resolveSign(Protocol proto)
{
if (!mSigKeys[proto].empty()) {
// Explicitly set
return;
}
const auto key = mCache->findBestByMailBox(mSender.toUtf8().constData(), proto, KeyCache::KeyUsage::Sign);
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find" << Formatting::displayName(proto) << "signing key for" << mSender;
return;
}
if (!isAcceptableSigningKey(key)) {
qCDebug(LIBKLEO_LOG) << "Unacceptable signing key" << key.primaryFingerprint() << "for" << mSender;
return;
}
mSigKeys.insert(proto, {key});
}
void KeyResolverCore::Private::setSigningKeys(const QStringList &fingerprints)
{
if (mSign) {
for (const auto &fpr : fingerprints) {
const auto key = mCache->findByKeyIDOrFingerprint(fpr.toUtf8().constData());
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find signing key with fingerprint" << fpr;
continue;
}
mSigKeys[key.protocol()].push_back(key);
}
}
}
std::vector<Key> KeyResolverCore::Private::resolveRecipientWithGroup(const QString &address, Protocol protocol)
{
const auto group = mCache->findGroup(address, protocol, KeyCache::KeyUsage::Encrypt);
if (group.isNull()) {
return {};
}
// If we have one unacceptable group key we reject the
// whole group to avoid the situation where one key is
// skipped or the operation fails.
//
// We are in Autoresolve land here. In the GUI we
// will also show unacceptable group keys so that the
// user can see which key is not acceptable.
const auto &keys = group.keys();
const bool allKeysAreAcceptable = std::all_of(std::begin(keys), std::end(keys), [this](const auto &key) {
return isAcceptableEncryptionKey(key);
});
if (!allKeysAreAcceptable) {
qCDebug(LIBKLEO_LOG) << "group" << group.name() << "has at least one unacceptable key";
return {};
}
for (const auto &k : keys) {
qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << k.primaryFingerprint();
}
std::vector<Key> result;
std::copy(std::begin(keys), std::end(keys), std::back_inserter(result));
return result;
}
void KeyResolverCore::Private::resolveEncryptionGroups()
{
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
if (!protocolKeysMap[UnknownProtocol].empty()) {
// already resolved by common override
continue;
}
if (mFormat == OpenPGP) {
if (!protocolKeysMap[OpenPGP].empty()) {
// already resolved by override
continue;
}
protocolKeysMap[OpenPGP] = resolveRecipientWithGroup(address, OpenPGP);
} else if (mFormat == CMS) {
if (!protocolKeysMap[CMS].empty()) {
// already resolved by override
continue;
}
protocolKeysMap[CMS] = resolveRecipientWithGroup(address, CMS);
} else {
// prefer single-protocol groups over mixed-protocol groups
const auto openPGPGroupKeys = resolveRecipientWithGroup(address, OpenPGP);
const auto smimeGroupKeys = resolveRecipientWithGroup(address, CMS);
if (!openPGPGroupKeys.empty() && !smimeGroupKeys.empty()) {
protocolKeysMap[OpenPGP] = openPGPGroupKeys;
protocolKeysMap[CMS] = smimeGroupKeys;
} else if (openPGPGroupKeys.empty() && smimeGroupKeys.empty()) {
// no single-protocol groups found;
// if mixed protocols are allowed, then look for any group with encryption keys
if (mAllowMixed) {
protocolKeysMap[UnknownProtocol] = resolveRecipientWithGroup(address, UnknownProtocol);
}
} else {
// there is a single-protocol group only for one protocol; use this group for all protocols
protocolKeysMap[UnknownProtocol] = !openPGPGroupKeys.empty() ? openPGPGroupKeys : smimeGroupKeys;
}
}
}
}
std::vector<Key> KeyResolverCore::Private::resolveRecipient(const QString &address, Protocol protocol)
{
const auto key = mCache->findBestByMailBox(address.toUtf8().constData(), protocol, KeyCache::KeyUsage::Encrypt);
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << "Failed to find any" << Formatting::displayName(protocol) << "key for:" << address;
return {};
}
if (!isAcceptableEncryptionKey(key, address)) {
qCDebug(LIBKLEO_LOG) << "key for:" << address << key.primaryFingerprint() << "has not enough validity";
return {};
}
qCDebug(LIBKLEO_LOG) << "Resolved encrypt to" << address << "with key" << key.primaryFingerprint();
return {key};
}
// Try to find matching keys in the provided protocol for the unresolved addresses
void KeyResolverCore::Private::resolveEnc(Protocol proto)
{
for (auto it = mEncKeys.begin(); it != mEncKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
if (!protocolKeysMap[proto].empty()) {
// already resolved for current protocol (by override or group)
continue;
}
const std::vector<Key> &commonOverrideOrGroup = protocolKeysMap[UnknownProtocol];
if (!commonOverrideOrGroup.empty()) {
// there is a common override or group; use it for current protocol if possible
if (allKeysHaveProtocol(commonOverrideOrGroup, proto)) {
protocolKeysMap[proto] = commonOverrideOrGroup;
continue;
} else {
qCDebug(LIBKLEO_LOG) << "Common override/group for" << address << "is unusable for" << Formatting::displayName(proto);
continue;
}
}
protocolKeysMap[proto] = resolveRecipient(address, proto);
}
}
auto getBestEncryptionKeys(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol preferredProtocol)
{
QMap<QString, std::vector<Key>> result;
for (auto it = encryptionKeys.begin(); it != encryptionKeys.end(); ++it) {
const QString &address = it.key();
auto &protocolKeysMap = it.value();
const std::vector<Key> &overrideKeys = protocolKeysMap[UnknownProtocol];
if (!overrideKeys.empty()) {
result.insert(address, overrideKeys);
continue;
}
const std::vector<Key> &keysOpenPGP = protocolKeysMap[OpenPGP];
const std::vector<Key> &keysCMS = protocolKeysMap[CMS];
if (keysOpenPGP.empty() && keysCMS.empty()) {
result.insert(address, {});
} else if (!keysOpenPGP.empty() && keysCMS.empty()) {
result.insert(address, keysOpenPGP);
} else if (keysOpenPGP.empty() && !keysCMS.empty()) {
result.insert(address, keysCMS);
} else {
// check whether OpenPGP keys or S/MIME keys have higher validity
const int validityPGP = minimumValidity(keysOpenPGP, address);
const int validityCMS = minimumValidity(keysCMS, address);
if ((validityCMS > validityPGP) || (validityCMS == validityPGP && preferredProtocol == CMS)) {
result.insert(address, keysCMS);
} else {
result.insert(address, keysOpenPGP);
}
}
}
return result;
}
namespace
{
bool hasUnresolvedSender(const QMap<Protocol, std::vector<Key>> &signingKeys, Protocol protocol)
{
return signingKeys.value(protocol).empty();
}
bool hasUnresolvedRecipients(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol](const auto &protocolKeysMap) {
return protocolKeysMap.value(protocol).empty();
});
}
bool anyCommonOverrideHasKeyOfType(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
return std::any_of(std::cbegin(encryptionKeys), std::cend(encryptionKeys), [protocol](const auto &protocolKeysMap) {
return anyKeyHasProtocol(protocolKeysMap.value(UnknownProtocol), protocol);
});
}
auto keysForProtocol(const QMap<QString, QMap<Protocol, std::vector<Key>>> &encryptionKeys, Protocol protocol)
{
QMap<QString, std::vector<Key>> keys;
for (auto it = std::begin(encryptionKeys), end = std::end(encryptionKeys); it != end; ++it) {
const QString &address = it.key();
const auto &protocolKeysMap = it.value();
keys.insert(address, protocolKeysMap.value(protocol));
}
return keys;
}
template<typename T>
auto concatenate(std::vector<T> v1, const std::vector<T> &v2)
{
v1.reserve(v1.size() + v2.size());
v1.insert(std::end(v1), std::begin(v2), std::end(v2));
return v1;
}
}
KeyResolverCore::Result KeyResolverCore::Private::resolve()
{
qCDebug(LIBKLEO_LOG) << "Starting ";
if (!mSign && !mEncrypt) {
// nothing to do
return {AllResolved, {}, {}};
}
// First resolve through overrides
resolveOverrides();
// check protocols needed for overrides
const bool commonOverridesNeedOpenPGP = anyCommonOverrideHasKeyOfType(mEncKeys, OpenPGP);
const bool commonOverridesNeedCMS = anyCommonOverrideHasKeyOfType(mEncKeys, CMS);
if ((mFormat == OpenPGP && commonOverridesNeedCMS) //
|| (mFormat == CMS && commonOverridesNeedOpenPGP) //
|| (!mAllowMixed && commonOverridesNeedOpenPGP && commonOverridesNeedCMS)) {
// invalid protocol requirements -> clear intermediate result and abort resolution
mEncKeys.clear();
return {Error, {}, {}};
}
// Next look for matching groups of keys
if (mSign) {
resolveSigningGroups();
}
if (mEncrypt) {
resolveEncryptionGroups();
}
// Then look for signing / encryption keys
if (mFormat == OpenPGP || mFormat == UnknownProtocol) {
resolveSign(OpenPGP);
resolveEnc(OpenPGP);
}
const bool pgpOnly = ((!mEncrypt || !hasUnresolvedRecipients(mEncKeys, OpenPGP)) //
&& (!mSign || !hasUnresolvedSender(mSigKeys, OpenPGP)));
if (mFormat == OpenPGP) {
return {
SolutionFlags((pgpOnly ? AllResolved : SomeUnresolved) | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{},
};
}
if (mFormat == CMS || mFormat == UnknownProtocol) {
resolveSign(CMS);
resolveEnc(CMS);
}
const bool cmsOnly = ((!mEncrypt || !hasUnresolvedRecipients(mEncKeys, CMS)) //
&& (!mSign || !hasUnresolvedSender(mSigKeys, CMS)));
if (mFormat == CMS) {
return {
SolutionFlags((cmsOnly ? AllResolved : SomeUnresolved) | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{},
};
}
// check if single-protocol solution has been found
if (cmsOnly && (!pgpOnly || mPreferredProtocol == CMS)) {
if (!mAllowMixed) {
return {
SolutionFlags(AllResolved | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
};
} else {
return {
SolutionFlags(AllResolved | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{},
};
}
}
if (pgpOnly) {
if (!mAllowMixed) {
return {
SolutionFlags(AllResolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
};
} else {
return {
SolutionFlags(AllResolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{},
};
}
}
if (!mAllowMixed) {
// return incomplete single-protocol solution
if (mPreferredProtocol == CMS) {
return {
SolutionFlags(SomeUnresolved | CMSOnly),
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
};
} else {
return {
SolutionFlags(SomeUnresolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), keysForProtocol(mEncKeys, OpenPGP)},
{CMS, mSigKeys.value(CMS), keysForProtocol(mEncKeys, CMS)},
};
}
}
const auto bestEncryptionKeys = getBestEncryptionKeys(mEncKeys, mPreferredProtocol);
// we are in mixed mode, i.e. we need an OpenPGP signing key and an S/MIME signing key
const bool senderIsResolved = (!mSign || (!hasUnresolvedSender(mSigKeys, OpenPGP) && !hasUnresolvedSender(mSigKeys, CMS)));
const bool allRecipientsAreResolved = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) {
return !keys.empty();
});
if (senderIsResolved && allRecipientsAreResolved) {
return {
SolutionFlags(AllResolved | MixedProtocols),
{UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys},
{},
};
}
const bool allKeysAreOpenPGP = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) {
return allKeysHaveProtocol(keys, OpenPGP);
});
if (allKeysAreOpenPGP) {
return {
SolutionFlags(SomeUnresolved | OpenPGPOnly),
{OpenPGP, mSigKeys.value(OpenPGP), bestEncryptionKeys},
{},
};
}
const bool allKeysAreCMS = std::all_of(std::begin(bestEncryptionKeys), std::end(bestEncryptionKeys), [](const auto &keys) {
return allKeysHaveProtocol(keys, CMS);
});
if (allKeysAreCMS) {
return {
SolutionFlags(SomeUnresolved | CMSOnly),
{CMS, mSigKeys.value(CMS), bestEncryptionKeys},
{},
};
}
return {
SolutionFlags(SomeUnresolved | MixedProtocols),
{UnknownProtocol, concatenate(mSigKeys.value(OpenPGP), mSigKeys.value(CMS)), bestEncryptionKeys},
{},
};
}
KeyResolverCore::KeyResolverCore(bool encrypt, bool sign, Protocol fmt)
: d(new Private(this, encrypt, sign, fmt))
{
}
KeyResolverCore::~KeyResolverCore() = default;
void KeyResolverCore::setSender(const QString &address)
{
d->setSender(address);
}
QString KeyResolverCore::normalizedSender() const
{
return d->mSender;
}
void KeyResolverCore::setRecipients(const QStringList &addresses)
{
d->addRecipients(addresses);
}
void KeyResolverCore::setSigningKeys(const QStringList &fingerprints)
{
d->setSigningKeys(fingerprints);
}
void KeyResolverCore::setOverrideKeys(const QMap<Protocol, QMap<QString, QStringList>> &overrides)
{
d->setOverrideKeys(overrides);
}
void KeyResolverCore::setAllowMixedProtocols(bool allowMixed)
{
d->mAllowMixed = allowMixed;
}
void KeyResolverCore::setPreferredProtocol(Protocol proto)
{
d->mPreferredProtocol = proto;
}
void KeyResolverCore::setMinimumValidity(int validity)
{
d->mMinimumValidity = validity;
}
KeyResolverCore::Result KeyResolverCore::resolve()
{
return d->resolve();
}
diff --git a/src/models/keycache.cpp b/src/models/keycache.cpp
index 172adfd5..7172379d 100644
--- a/src/models/keycache.cpp
+++ b/src/models/keycache.cpp
@@ -1,1738 +1,1739 @@
/* -*- mode: c++; c-basic-offset:4 -*-
models/keycache.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2020, 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keycache.h"
#include "keycache_p.h"
#include "kleo/dn.h"
#include "kleo/enum.h"
#include "kleo/keygroup.h"
#include "kleo/keygroupconfig.h"
#include "kleo/predicates.h"
#include "kleo/stl_util.h"
#include "utils/algorithm.h"
#include "utils/compat.h"
#include "utils/filesystemwatcher.h"
#include "utils/qtstlhelpers.h"
+#include <libkleo_debug.h>
+
#include <KConfigGroup>
#include <KSharedConfig>
#include <gpgme++/context.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme++/verificationresult.h>
#include <qgpgme/cryptoconfig.h>
#include <qgpgme/listallkeysjob.h>
#include <qgpgme/protocol.h>
#include <gpg-error.h>
//#include <KMime/HeaderParsing>
#include <QEventLoop>
#include <QPointer>
#include <QTimer>
#include <algorithm>
#include <functional>
#include <iterator>
#include <utility>
#include "kleo/debug.h"
-#include "libkleo_debug.h"
#include <chrono>
using namespace std::chrono_literals;
using namespace Kleo;
using namespace GpgME;
using namespace KMime::Types;
static const unsigned int hours2ms = 1000 * 60 * 60;
//
//
// KeyCache
//
//
namespace
{
make_comparator_str(ByEMail, .first.c_str());
}
class Kleo::KeyCacheAutoRefreshSuspension
{
KeyCacheAutoRefreshSuspension()
{
qCDebug(LIBKLEO_LOG) << __func__;
auto cache = KeyCache::mutableInstance();
cache->enableFileSystemWatcher(false);
m_refreshInterval = cache->refreshInterval();
cache->setRefreshInterval(0);
cache->cancelKeyListing();
m_cache = cache;
}
public:
~KeyCacheAutoRefreshSuspension()
{
qCDebug(LIBKLEO_LOG) << __func__;
if (auto cache = m_cache.lock()) {
cache->enableFileSystemWatcher(true);
cache->setRefreshInterval(m_refreshInterval);
}
}
static std::shared_ptr<KeyCacheAutoRefreshSuspension> instance()
{
static std::weak_ptr<KeyCacheAutoRefreshSuspension> self;
if (auto s = self.lock()) {
return s;
} else {
s = std::shared_ptr<KeyCacheAutoRefreshSuspension>{new KeyCacheAutoRefreshSuspension{}};
self = s;
return s;
}
}
private:
std::weak_ptr<KeyCache> m_cache;
int m_refreshInterval = 0;
};
class KeyCache::Private
{
friend class ::Kleo::KeyCache;
KeyCache *const q;
public:
explicit Private(KeyCache *qq)
: q(qq)
, m_refreshInterval(1)
, m_initalized(false)
, m_pgpOnly(true)
, m_remarks_enabled(false)
{
connect(&m_autoKeyListingTimer, &QTimer::timeout, q, [this]() {
q->startKeyListing();
});
updateAutoKeyListingTimer();
}
~Private()
{
if (m_refreshJob) {
m_refreshJob->cancel();
}
}
template<template<template<typename U> class Op> class Comp>
std::vector<Key>::const_iterator find(const std::vector<Key> &keys, const char *key) const
{
ensureCachePopulated();
const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
return it;
} else {
return keys.end();
}
}
template<template<template<typename U> class Op> class Comp>
std::vector<Subkey>::const_iterator find(const std::vector<Subkey> &keys, const char *key) const
{
ensureCachePopulated();
const auto it = std::lower_bound(keys.begin(), keys.end(), key, Comp<std::less>());
if (it == keys.end() || Comp<std::equal_to>()(*it, key)) {
return it;
} else {
return keys.end();
}
}
std::vector<Key>::const_iterator find_fpr(const char *fpr) const
{
return find<_detail::ByFingerprint>(by.fpr, fpr);
}
std::pair<std::vector<std::pair<std::string, Key>>::const_iterator, std::vector<std::pair<std::string, Key>>::const_iterator>
find_email(const char *email) const
{
ensureCachePopulated();
return std::equal_range(by.email.begin(), by.email.end(), email, ByEMail<std::less>());
}
std::vector<Key> find_mailbox(const QString &email, bool sign) const;
std::vector<Subkey>::const_iterator find_keygrip(const char *keygrip) const
{
return find<_detail::ByKeyGrip>(by.keygrip, keygrip);
}
std::vector<Subkey>::const_iterator find_subkeyid(const char *subkeyid) const
{
return find<_detail::ByKeyID>(by.subkeyid, subkeyid);
}
std::vector<Key>::const_iterator find_keyid(const char *keyid) const
{
return find<_detail::ByKeyID>(by.keyid, keyid);
}
std::vector<Key>::const_iterator find_shortkeyid(const char *shortkeyid) const
{
return find<_detail::ByShortKeyID>(by.shortkeyid, shortkeyid);
}
std::pair<std::vector<Key>::const_iterator, std::vector<Key>::const_iterator> find_subjects(const char *chain_id) const
{
ensureCachePopulated();
return std::equal_range(by.chainid.begin(), by.chainid.end(), chain_id, _detail::ByChainID<std::less>());
}
void refreshJobDone(const KeyListResult &result);
void setRefreshInterval(int interval)
{
m_refreshInterval = interval;
updateAutoKeyListingTimer();
}
int refreshInterval() const
{
return m_refreshInterval;
}
void updateAutoKeyListingTimer()
{
setAutoKeyListingInterval(hours2ms * m_refreshInterval);
}
void setAutoKeyListingInterval(int ms)
{
m_autoKeyListingTimer.stop();
m_autoKeyListingTimer.setInterval(ms);
if (ms != 0) {
m_autoKeyListingTimer.start();
}
}
void ensureCachePopulated() const;
void readGroupsFromGpgConf()
{
// According to Werner Koch groups are more of a hack to solve
// a valid usecase (e.g. several keys defined for an internal mailing list)
// that won't make it in the proper keylist interface. And using gpgconf
// was the suggested way to support groups.
auto conf = QGpgME::cryptoConfig();
if (!conf) {
return;
}
auto entry = getCryptoConfigEntry(conf, "gpg", "group");
if (!entry) {
return;
}
// collect the key fingerprints for all groups read from the configuration
QMap<QString, QStringList> fingerprints;
const auto stringValueList = entry->stringValueList();
for (const QString &value : stringValueList) {
const QStringList split = value.split(QLatin1Char('='));
if (split.size() != 2) {
qCDebug(LIBKLEO_LOG) << "Ignoring invalid group config:" << value;
continue;
}
const QString groupName = split[0];
const QString fingerprint = split[1];
fingerprints[groupName].push_back(fingerprint);
}
// add all groups read from the configuration to the list of groups
for (auto it = fingerprints.cbegin(); it != fingerprints.cend(); ++it) {
const QString groupName = it.key();
const std::vector<Key> groupKeys = q->findByFingerprint(toStdStrings(it.value()));
KeyGroup g(groupName, groupName, groupKeys, KeyGroup::GnuPGConfig);
m_groups.push_back(g);
}
}
void readGroupsFromGroupsConfig()
{
Q_ASSERT(m_groupConfig);
if (!m_groupConfig) {
qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
return;
}
m_groups = m_groupConfig->readGroups();
}
KeyGroup writeGroupToGroupsConfig(const KeyGroup &group)
{
Q_ASSERT(m_groupConfig);
if (!m_groupConfig) {
qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
return {};
}
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be written to application configuration:" << group;
return group;
}
return m_groupConfig->writeGroup(group);
}
bool removeGroupFromGroupsConfig(const KeyGroup &group)
{
Q_ASSERT(m_groupConfig);
if (!m_groupConfig) {
qCWarning(LIBKLEO_LOG) << __func__ << "group config not set";
return false;
}
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << __func__ << "group cannot be removed from application configuration:" << group;
return false;
}
return m_groupConfig->removeGroup(group);
}
void updateGroupCache()
{
// Update Group Keys
// this is a quick thing as it only involves reading the config
// so no need for a job.
m_groups.clear();
if (m_groupsEnabled) {
readGroupsFromGpgConf();
readGroupsFromGroupsConfig();
}
}
bool insert(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it != m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Group already present in list of groups:" << group;
return false;
}
const KeyGroup savedGroup = writeGroupToGroupsConfig(group);
if (savedGroup.isNull()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::insert - Writing group" << group.id() << "to config file failed";
return false;
}
m_groups.push_back(savedGroup);
Q_EMIT q->groupAdded(savedGroup);
return true;
}
bool update(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it == m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Group not found in list of groups:" << group;
return false;
}
const auto groupIndex = std::distance(m_groups.cbegin(), it);
const KeyGroup savedGroup = writeGroupToGroupsConfig(group);
if (savedGroup.isNull()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::update - Writing group" << group.id() << "to config file failed";
return false;
}
m_groups[groupIndex] = savedGroup;
Q_EMIT q->groupUpdated(savedGroup);
return true;
}
bool remove(const KeyGroup &group)
{
Q_ASSERT(!group.isNull());
Q_ASSERT(group.source() == KeyGroup::ApplicationConfig);
if (group.isNull() || group.source() != KeyGroup::ApplicationConfig) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Invalid group:" << group;
return false;
}
const auto it = std::find_if(m_groups.cbegin(), m_groups.cend(), [group](const auto &g) {
return g.source() == group.source() && g.id() == group.id();
});
if (it == m_groups.cend()) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Group not found in list of groups:" << group;
return false;
}
const bool success = removeGroupFromGroupsConfig(group);
if (!success) {
qCDebug(LIBKLEO_LOG) << "KeyCache::Private::remove - Removing group" << group.id() << "from config file failed";
return false;
}
m_groups.erase(it);
Q_EMIT q->groupRemoved(group);
return true;
}
private:
QPointer<RefreshKeysJob> m_refreshJob;
std::vector<std::shared_ptr<FileSystemWatcher>> m_fsWatchers;
QTimer m_autoKeyListingTimer;
int m_refreshInterval;
struct By {
std::vector<Key> fpr, keyid, shortkeyid, chainid;
std::vector<std::pair<std::string, Key>> email;
std::vector<Subkey> subkeyid, keygrip;
} by;
bool m_initalized;
bool m_pgpOnly;
bool m_remarks_enabled;
bool m_groupsEnabled = false;
std::shared_ptr<KeyGroupConfig> m_groupConfig;
std::vector<KeyGroup> m_groups;
};
std::shared_ptr<const KeyCache> KeyCache::instance()
{
return mutableInstance();
}
std::shared_ptr<KeyCache> KeyCache::mutableInstance()
{
static std::weak_ptr<KeyCache> self;
try {
return std::shared_ptr<KeyCache>(self);
} catch (const std::bad_weak_ptr &) {
const std::shared_ptr<KeyCache> s(new KeyCache);
self = s;
return s;
}
}
KeyCache::KeyCache()
: QObject()
, d(new Private(this))
{
}
KeyCache::~KeyCache()
{
}
void KeyCache::setGroupsEnabled(bool enabled)
{
d->m_groupsEnabled = enabled;
if (d->m_initalized) {
d->updateGroupCache();
}
}
void KeyCache::setGroupConfig(const std::shared_ptr<KeyGroupConfig> &groupConfig)
{
d->m_groupConfig = groupConfig;
}
void KeyCache::enableFileSystemWatcher(bool enable)
{
for (const auto &i : std::as_const(d->m_fsWatchers)) {
i->setEnabled(enable);
}
}
void KeyCache::setRefreshInterval(int hours)
{
d->setRefreshInterval(hours);
}
int KeyCache::refreshInterval() const
{
return d->refreshInterval();
}
std::shared_ptr<KeyCacheAutoRefreshSuspension> KeyCache::suspendAutoRefresh()
{
return KeyCacheAutoRefreshSuspension::instance();
}
void KeyCache::reload(GpgME::Protocol /*proto*/)
{
if (d->m_refreshJob) {
return;
}
d->updateAutoKeyListingTimer();
enableFileSystemWatcher(false);
d->m_refreshJob = new RefreshKeysJob(this);
connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &r) {
d->refreshJobDone(r);
});
connect(d->m_refreshJob.data(), &RefreshKeysJob::canceled, this, [this]() {
d->m_refreshJob.clear();
});
d->m_refreshJob->start();
}
void KeyCache::cancelKeyListing()
{
if (!d->m_refreshJob) {
return;
}
d->m_refreshJob->cancel();
}
void KeyCache::addFileSystemWatcher(const std::shared_ptr<FileSystemWatcher> &watcher)
{
if (!watcher) {
return;
}
d->m_fsWatchers.push_back(watcher);
connect(watcher.get(), &FileSystemWatcher::directoryChanged, this, [this]() {
startKeyListing();
});
connect(watcher.get(), &FileSystemWatcher::fileChanged, this, [this]() {
startKeyListing();
});
watcher->setEnabled(d->m_refreshJob.isNull());
}
void KeyCache::enableRemarks(bool value)
{
if (!d->m_remarks_enabled && value) {
d->m_remarks_enabled = value;
if (d->m_initalized && !d->m_refreshJob) {
qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
reload();
} else {
connect(d->m_refreshJob.data(), &RefreshKeysJob::done, this, [this](const GpgME::KeyListResult &) {
qCDebug(LIBKLEO_LOG) << "Reloading keycache with remarks enabled";
QTimer::singleShot(1s, this, [this]() {
reload();
});
});
}
} else {
d->m_remarks_enabled = value;
}
}
bool KeyCache::remarksEnabled() const
{
return d->m_remarks_enabled;
}
void KeyCache::Private::refreshJobDone(const KeyListResult &result)
{
m_refreshJob.clear();
q->enableFileSystemWatcher(true);
m_initalized = true;
updateGroupCache();
Q_EMIT q->keyListingDone(result);
}
const Key &KeyCache::findByFingerprint(const char *fpr) const
{
const std::vector<Key>::const_iterator it = d->find_fpr(fpr);
if (it == d->by.fpr.end()) {
static const Key null;
return null;
} else {
return *it;
}
}
const Key &KeyCache::findByFingerprint(const std::string &fpr) const
{
return findByFingerprint(fpr.c_str());
}
std::vector<GpgME::Key> KeyCache::findByFingerprint(const std::vector<std::string> &fprs) const
{
std::vector<Key> keys;
keys.reserve(fprs.size());
for (const auto &fpr : fprs) {
const Key key = findByFingerprint(fpr.c_str());
if (key.isNull()) {
qCDebug(LIBKLEO_LOG) << __func__ << "Ignoring unknown key with fingerprint:" << fpr.c_str();
continue;
}
keys.push_back(key);
}
return keys;
}
std::vector<Key> KeyCache::findByEMailAddress(const char *email) const
{
const auto pair = d->find_email(email);
std::vector<Key> result;
result.reserve(std::distance(pair.first, pair.second));
std::transform(pair.first, pair.second, std::back_inserter(result), [](const std::pair<std::string, Key> &pair) {
return pair.second;
});
return result;
}
std::vector<Key> KeyCache::findByEMailAddress(const std::string &email) const
{
return findByEMailAddress(email.c_str());
}
const Key &KeyCache::findByShortKeyID(const char *id) const
{
const std::vector<Key>::const_iterator it = d->find_shortkeyid(id);
if (it != d->by.shortkeyid.end()) {
return *it;
}
static const Key null;
return null;
}
const Key &KeyCache::findByShortKeyID(const std::string &id) const
{
return findByShortKeyID(id.c_str());
}
const Key &KeyCache::findByKeyIDOrFingerprint(const char *id) const
{
{
// try by.fpr first:
const std::vector<Key>::const_iterator it = d->find_fpr(id);
if (it != d->by.fpr.end()) {
return *it;
}
}
{
// try by.keyid next:
const std::vector<Key>::const_iterator it = d->find_keyid(id);
if (it != d->by.keyid.end()) {
return *it;
}
}
static const Key null;
return null;
}
const Key &KeyCache::findByKeyIDOrFingerprint(const std::string &id) const
{
return findByKeyIDOrFingerprint(id.c_str());
}
std::vector<Key> KeyCache::findByKeyIDOrFingerprint(const std::vector<std::string> &ids) const
{
std::vector<std::string> keyids;
std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(keyids), [](const std::string &str) {
return !str.c_str() || !*str.c_str();
});
// this is just case-insensitive string search:
std::sort(keyids.begin(), keyids.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> result;
result.reserve(keyids.size()); // dups shouldn't happen
d->ensureCachePopulated();
kdtools::set_intersection(d->by.fpr.begin(),
d->by.fpr.end(),
keyids.begin(),
keyids.end(),
std::back_inserter(result),
_detail::ByFingerprint<std::less>());
if (result.size() < keyids.size()) {
// note that By{Fingerprint,KeyID,ShortKeyID} define the same
// order for _strings_
kdtools::set_intersection(d->by.keyid.begin(),
d->by.keyid.end(),
keyids.begin(),
keyids.end(),
std::back_inserter(result),
_detail::ByKeyID<std::less>());
}
// duplicates shouldn't happen, but make sure nonetheless:
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
// we skip looking into short key ids here, as it's highly
// unlikely they're used for this purpose. We might need to revise
// this decision, but only after testing.
return result;
}
const Subkey &KeyCache::findSubkeyByKeyGrip(const char *grip, Protocol protocol) const
{
static const Subkey null;
d->ensureCachePopulated();
const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), grip, _detail::ByKeyGrip<std::less>());
if (range.first == range.second) {
return null;
} else if (protocol == UnknownProtocol) {
return *range.first;
} else {
for (auto it = range.first; it != range.second; ++it) {
if (it->parent().protocol() == protocol) {
return *it;
}
}
}
return null;
}
const Subkey &KeyCache::findSubkeyByKeyGrip(const std::string &grip, Protocol protocol) const
{
return findSubkeyByKeyGrip(grip.c_str(), protocol);
}
std::vector<Subkey> KeyCache::findSubkeysByKeyID(const std::vector<std::string> &ids) const
{
std::vector<std::string> sorted;
sorted.reserve(ids.size());
std::remove_copy_if(ids.begin(), ids.end(), std::back_inserter(sorted), [](const std::string &str) {
return !str.c_str() || !*str.c_str();
});
std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
std::vector<Subkey> result;
d->ensureCachePopulated();
kdtools::set_intersection(d->by.subkeyid.begin(),
d->by.subkeyid.end(),
sorted.begin(),
sorted.end(),
std::back_inserter(result),
_detail::ByKeyID<std::less>());
return result;
}
std::vector<Key> KeyCache::findRecipients(const DecryptionResult &res) const
{
std::vector<std::string> keyids;
const auto recipients = res.recipients();
for (const DecryptionResult::Recipient &r : recipients) {
if (const char *kid = r.keyID()) {
keyids.push_back(kid);
}
}
const std::vector<Subkey> subkeys = findSubkeysByKeyID(keyids);
std::vector<Key> result;
result.reserve(subkeys.size());
std::transform(subkeys.begin(), subkeys.end(), std::back_inserter(result), std::mem_fn(&Subkey::parent));
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
return result;
}
std::vector<Key> KeyCache::findSigners(const VerificationResult &res) const
{
std::vector<std::string> fprs;
const auto signatures = res.signatures();
for (const Signature &s : signatures) {
if (const char *fpr = s.fingerprint()) {
fprs.push_back(fpr);
}
}
return findByKeyIDOrFingerprint(fprs);
}
std::vector<Key> KeyCache::findSigningKeysByMailbox(const QString &mb) const
{
return d->find_mailbox(mb, true);
}
std::vector<Key> KeyCache::findEncryptionKeysByMailbox(const QString &mb) const
{
return d->find_mailbox(mb, false);
}
namespace
{
#define DO(op, meth, meth2) \
if (op key.meth()) { \
} else { \
qDebug("rejecting for signing: %s: %s", #meth2, key.primaryFingerprint()); \
return false; \
}
#define ACCEPT(meth) DO(!!, meth, !meth)
#define REJECT(meth) DO(!, meth, meth)
struct ready_for_signing {
bool operator()(const Key &key) const
{
#if 1
ACCEPT(hasSecret);
ACCEPT(canReallySign);
REJECT(isRevoked);
REJECT(isExpired);
REJECT(isDisabled);
REJECT(isInvalid);
return true;
#else
return key.hasSecret() && key.canReallySign() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
#endif
#undef DO
}
};
#define DO(op, meth, meth2) \
if (op key.meth()) { \
} else { \
qDebug("rejecting for encrypting: %s: %s", #meth2, key.primaryFingerprint()); \
return false; \
}
struct ready_for_encryption {
bool operator()(const Key &key) const
{
#if 1
ACCEPT(canEncrypt);
REJECT(isRevoked);
REJECT(isExpired);
REJECT(isDisabled);
REJECT(isInvalid);
return true;
#else
return key.canEncrypt() && !key.isRevoked() && !key.isExpired() && !key.isDisabled() && !key.isInvalid();
#endif
}
#undef DO
#undef ACCEPT
#undef REJECT
};
}
std::vector<Key> KeyCache::Private::find_mailbox(const QString &email, bool sign) const
{
if (email.isEmpty()) {
return std::vector<Key>();
}
const auto pair = find_email(email.toUtf8().constData());
std::vector<Key> result;
result.reserve(std::distance(pair.first, pair.second));
if (sign) {
kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_signing());
} else {
kdtools::copy_2nd_if(pair.first, pair.second, std::back_inserter(result), ready_for_encryption());
}
return result;
}
std::vector<Key> KeyCache::findSubjects(const GpgME::Key &key, Options options) const
{
return findSubjects(std::vector<Key>(1, key), options);
}
std::vector<Key> KeyCache::findSubjects(const std::vector<Key> &keys, Options options) const
{
return findSubjects(keys.begin(), keys.end(), options);
}
std::vector<Key> KeyCache::findSubjects(std::vector<Key>::const_iterator first, std::vector<Key>::const_iterator last, Options options) const
{
if (first == last) {
return std::vector<Key>();
}
std::vector<Key> result;
while (first != last) {
const auto pair = d->find_subjects(first->primaryFingerprint());
result.insert(result.end(), pair.first, pair.second);
++first;
}
std::sort(result.begin(), result.end(), _detail::ByFingerprint<std::less>());
result.erase(std::unique(result.begin(), result.end(), _detail::ByFingerprint<std::equal_to>()), result.end());
if (options & RecursiveSearch) {
const std::vector<Key> furtherSubjects = findSubjects(result, options);
std::vector<Key> combined;
combined.reserve(result.size() + furtherSubjects.size());
std::merge(result.begin(),
result.end(),
furtherSubjects.begin(),
furtherSubjects.end(),
std::back_inserter(combined),
_detail::ByFingerprint<std::less>());
combined.erase(std::unique(combined.begin(), combined.end(), _detail::ByFingerprint<std::equal_to>()), combined.end());
result.swap(combined);
}
return result;
}
std::vector<Key> KeyCache::findIssuers(const Key &key, Options options) const
{
std::vector<Key> result;
if (key.isNull()) {
return result;
}
if (options & IncludeSubject) {
result.push_back(key);
}
if (key.isRoot()) {
return result;
}
const Key &issuer = findByFingerprint(key.chainID());
if (issuer.isNull()) {
return result;
}
result.push_back(issuer);
if (!(options & RecursiveSearch)) {
return result;
}
while (true) {
const Key &issuer = findByFingerprint(result.back().chainID());
if (issuer.isNull()) {
break;
}
const bool chainAlreadyContainsIssuer = Kleo::contains_if(result, [issuer](const auto &key) {
return _detail::ByFingerprint<std::equal_to>()(issuer, key);
});
// we also add the issuer if the chain already contains it, so that
// the user can spot the recursion
result.push_back(issuer);
if (issuer.isRoot() || chainAlreadyContainsIssuer) {
break;
}
}
return result;
}
static std::string email(const UserID &uid)
{
// Prefer the gnupg normalized one
const std::string addr = uid.addrSpec();
if (!addr.empty()) {
return addr;
}
const std::string email = uid.email();
if (email.empty()) {
return DN(uid.id())[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
}
if (email[0] == '<' && email[email.size() - 1] == '>') {
return email.substr(1, email.size() - 2);
} else {
return email;
}
}
static std::vector<std::string> emails(const Key &key)
{
std::vector<std::string> emails;
const auto userIDs = key.userIDs();
for (const UserID &uid : userIDs) {
const std::string e = email(uid);
if (!e.empty()) {
emails.push_back(e);
}
}
std::sort(emails.begin(), emails.end(), ByEMail<std::less>());
emails.erase(std::unique(emails.begin(), emails.end(), ByEMail<std::equal_to>()), emails.end());
return emails;
}
void KeyCache::remove(const Key &key)
{
if (key.isNull()) {
return;
}
const char *fpr = key.primaryFingerprint();
if (!fpr) {
return;
}
Q_EMIT aboutToRemove(key);
{
const auto range = std::equal_range(d->by.fpr.begin(), d->by.fpr.end(), fpr, _detail::ByFingerprint<std::less>());
d->by.fpr.erase(range.first, range.second);
}
if (const char *keyid = key.keyID()) {
const auto range = std::equal_range(d->by.keyid.begin(), d->by.keyid.end(), keyid, _detail::ByKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
return _detail::ByFingerprint<std::equal_to>()(fpr, key);
});
d->by.keyid.erase(it, range.second);
}
if (const char *shortkeyid = key.shortKeyID()) {
const auto range = std::equal_range(d->by.shortkeyid.begin(), d->by.shortkeyid.end(), shortkeyid, _detail::ByShortKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const GpgME::Key &key) {
return _detail::ByFingerprint<std::equal_to>()(fpr, key);
});
d->by.shortkeyid.erase(it, range.second);
}
if (const char *chainid = key.chainID()) {
const auto range = std::equal_range(d->by.chainid.begin(), d->by.chainid.end(), chainid, _detail::ByChainID<std::less>());
const auto range2 = std::equal_range(range.first, range.second, fpr, _detail::ByFingerprint<std::less>());
d->by.chainid.erase(range2.first, range2.second);
}
const auto emailsKey{emails(key)};
for (const std::string &email : emailsKey) {
const auto range = std::equal_range(d->by.email.begin(), d->by.email.end(), email, ByEMail<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const std::pair<std::string, Key> &pair) {
return qstricmp(fpr, pair.second.primaryFingerprint()) == 0;
});
d->by.email.erase(it, range.second);
}
const auto keySubKeys{key.subkeys()};
for (const Subkey &subkey : keySubKeys) {
if (const char *keyid = subkey.keyID()) {
const auto range = std::equal_range(d->by.subkeyid.begin(), d->by.subkeyid.end(), keyid, _detail::ByKeyID<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
return !qstricmp(fpr, subkey.parent().primaryFingerprint());
});
d->by.subkeyid.erase(it, range.second);
}
if (const char *keygrip = subkey.keyGrip()) {
const auto range = std::equal_range(d->by.keygrip.begin(), d->by.keygrip.end(), keygrip, _detail::ByKeyGrip<std::less>());
const auto it = std::remove_if(range.first, range.second, [fpr](const Subkey &subkey) {
return !qstricmp(fpr, subkey.parent().primaryFingerprint());
});
d->by.keygrip.erase(it, range.second);
}
}
}
void KeyCache::remove(const std::vector<Key> &keys)
{
for (const Key &key : keys) {
remove(key);
}
}
const std::vector<GpgME::Key> &KeyCache::keys() const
{
d->ensureCachePopulated();
return d->by.fpr;
}
std::vector<Key> KeyCache::secretKeys() const
{
std::vector<Key> keys = this->keys();
keys.erase(std::remove_if(keys.begin(),
keys.end(),
[](const Key &key) {
return !key.hasSecret();
}),
keys.end());
return keys;
}
KeyGroup KeyCache::group(const QString &id) const
{
KeyGroup result{};
const auto it = std::find_if(std::cbegin(d->m_groups), std::cend(d->m_groups), [id](const auto &g) {
return g.id() == id;
});
if (it != std::cend(d->m_groups)) {
result = *it;
}
return result;
}
std::vector<KeyGroup> KeyCache::groups() const
{
d->ensureCachePopulated();
return d->m_groups;
}
std::vector<KeyGroup> KeyCache::configurableGroups() const
{
std::vector<KeyGroup> groups;
groups.reserve(d->m_groups.size());
std::copy_if(d->m_groups.cbegin(), d->m_groups.cend(), std::back_inserter(groups), [](const KeyGroup &group) {
return group.source() == KeyGroup::ApplicationConfig;
});
return groups;
}
namespace
{
bool compareById(const KeyGroup &lhs, const KeyGroup &rhs)
{
return lhs.id() < rhs.id();
}
std::vector<KeyGroup> sortedById(std::vector<KeyGroup> groups)
{
std::sort(groups.begin(), groups.end(), &compareById);
return groups;
}
}
void KeyCache::saveConfigurableGroups(const std::vector<KeyGroup> &groups)
{
const std::vector<KeyGroup> oldGroups = sortedById(configurableGroups());
const std::vector<KeyGroup> newGroups = sortedById(groups);
{
std::vector<KeyGroup> removedGroups;
std::set_difference(oldGroups.begin(), oldGroups.end(), newGroups.begin(), newGroups.end(), std::back_inserter(removedGroups), &compareById);
for (const auto &group : std::as_const(removedGroups)) {
qCDebug(LIBKLEO_LOG) << "Removing group" << group;
d->remove(group);
}
}
{
std::vector<KeyGroup> updatedGroups;
std::set_intersection(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(updatedGroups), &compareById);
for (const auto &group : std::as_const(updatedGroups)) {
qCDebug(LIBKLEO_LOG) << "Updating group" << group;
d->update(group);
}
}
{
std::vector<KeyGroup> addedGroups;
std::set_difference(newGroups.begin(), newGroups.end(), oldGroups.begin(), oldGroups.end(), std::back_inserter(addedGroups), &compareById);
for (const auto &group : std::as_const(addedGroups)) {
qCDebug(LIBKLEO_LOG) << "Adding group" << group;
d->insert(group);
}
}
Q_EMIT keysMayHaveChanged();
}
bool KeyCache::insert(const KeyGroup &group)
{
if (!d->insert(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
bool KeyCache::update(const KeyGroup &group)
{
if (!d->update(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
bool KeyCache::remove(const KeyGroup &group)
{
if (!d->remove(group)) {
return false;
}
Q_EMIT keysMayHaveChanged();
return true;
}
void KeyCache::refresh(const std::vector<Key> &keys)
{
// make this better...
clear();
insert(keys);
}
void KeyCache::insert(const Key &key)
{
insert(std::vector<Key>(1, key));
}
namespace
{
template<template<template<typename T> class Op> class T1, template<template<typename T> class Op> class T2>
struct lexicographically {
using result_type = bool;
template<typename U, typename V>
bool operator()(const U &lhs, const V &rhs) const
{
return T1<std::less>()(lhs, rhs) //
|| (T1<std::equal_to>()(lhs, rhs) && T2<std::less>()(lhs, rhs));
}
};
}
void KeyCache::insert(const std::vector<Key> &keys)
{
// 1. remove those with empty fingerprints:
std::vector<Key> sorted;
sorted.reserve(keys.size());
std::remove_copy_if(keys.begin(), keys.end(), std::back_inserter(sorted), [](const Key &key) {
auto fp = key.primaryFingerprint();
return !fp || !*fp;
});
Q_FOREACH (const Key &key, sorted) {
remove(key); // this is sub-optimal, but makes implementation from here on much easier
}
// 2. sort by fingerprint:
std::sort(sorted.begin(), sorted.end(), _detail::ByFingerprint<std::less>());
// 2a. insert into fpr index:
std::vector<Key> by_fpr;
by_fpr.reserve(sorted.size() + d->by.fpr.size());
std::merge(sorted.begin(), sorted.end(), d->by.fpr.begin(), d->by.fpr.end(), std::back_inserter(by_fpr), _detail::ByFingerprint<std::less>());
// 3. build email index:
std::vector<std::pair<std::string, Key>> pairs;
pairs.reserve(sorted.size());
for (const Key &key : std::as_const(sorted)) {
const std::vector<std::string> emails = ::emails(key);
for (const std::string &e : emails) {
pairs.push_back(std::make_pair(e, key));
}
}
std::sort(pairs.begin(), pairs.end(), ByEMail<std::less>());
// 3a. insert into email index:
std::vector<std::pair<std::string, Key>> by_email;
by_email.reserve(pairs.size() + d->by.email.size());
std::merge(pairs.begin(), pairs.end(), d->by.email.begin(), d->by.email.end(), std::back_inserter(by_email), ByEMail<std::less>());
// 3.5: stable-sort by chain-id (effectively lexicographically<ByChainID,ByFingerprint>)
std::stable_sort(sorted.begin(), sorted.end(), _detail::ByChainID<std::less>());
// 3.5a: insert into chain-id index:
std::vector<Key> nonroot;
nonroot.reserve(sorted.size());
std::vector<Key> by_chainid;
by_chainid.reserve(sorted.size() + d->by.chainid.size());
std::copy_if(sorted.cbegin(), sorted.cend(), std::back_inserter(nonroot), [](const Key &key) {
return !key.isRoot();
});
std::merge(nonroot.cbegin(),
nonroot.cend(),
d->by.chainid.cbegin(),
d->by.chainid.cend(),
std::back_inserter(by_chainid),
lexicographically<_detail::ByChainID, _detail::ByFingerprint>());
// 4. sort by key id:
std::sort(sorted.begin(), sorted.end(), _detail::ByKeyID<std::less>());
// 4a. insert into keyid index:
std::vector<Key> by_keyid;
by_keyid.reserve(sorted.size() + d->by.keyid.size());
std::merge(sorted.begin(), sorted.end(), d->by.keyid.begin(), d->by.keyid.end(), std::back_inserter(by_keyid), _detail::ByKeyID<std::less>());
// 5. sort by short key id:
std::sort(sorted.begin(), sorted.end(), _detail::ByShortKeyID<std::less>());
// 5a. insert into short keyid index:
std::vector<Key> by_shortkeyid;
by_shortkeyid.reserve(sorted.size() + d->by.shortkeyid.size());
std::merge(sorted.begin(),
sorted.end(),
d->by.shortkeyid.begin(),
d->by.shortkeyid.end(),
std::back_inserter(by_shortkeyid),
_detail::ByShortKeyID<std::less>());
// 6. build subkey ID index:
std::vector<Subkey> subkeys;
subkeys.reserve(sorted.size());
for (const Key &key : std::as_const(sorted)) {
const auto keySubkeys{key.subkeys()};
for (const Subkey &subkey : keySubkeys) {
subkeys.push_back(subkey);
}
}
// 6a sort by key id:
std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyID<std::less>());
// 6b. insert into subkey ID index:
std::vector<Subkey> by_subkeyid;
by_subkeyid.reserve(subkeys.size() + d->by.subkeyid.size());
std::merge(subkeys.begin(), subkeys.end(), d->by.subkeyid.begin(), d->by.subkeyid.end(), std::back_inserter(by_subkeyid), _detail::ByKeyID<std::less>());
// 6c. sort by key grip
std::sort(subkeys.begin(), subkeys.end(), _detail::ByKeyGrip<std::less>());
// 6d. insert into subkey keygrip index:
std::vector<Subkey> by_keygrip;
by_keygrip.reserve(subkeys.size() + d->by.keygrip.size());
std::merge(subkeys.begin(), subkeys.end(), d->by.keygrip.begin(), d->by.keygrip.end(), std::back_inserter(by_keygrip), _detail::ByKeyGrip<std::less>());
// now commit (well, we already removed keys...)
by_fpr.swap(d->by.fpr);
by_keyid.swap(d->by.keyid);
by_shortkeyid.swap(d->by.shortkeyid);
by_email.swap(d->by.email);
by_subkeyid.swap(d->by.subkeyid);
by_keygrip.swap(d->by.keygrip);
by_chainid.swap(d->by.chainid);
for (const Key &key : std::as_const(sorted)) {
d->m_pgpOnly &= key.protocol() == GpgME::OpenPGP;
Q_EMIT added(key);
}
Q_EMIT keysMayHaveChanged();
}
void KeyCache::clear()
{
d->by = Private::By();
}
//
//
// RefreshKeysJob
//
//
class KeyCache::RefreshKeysJob::Private
{
RefreshKeysJob *const q;
public:
Private(KeyCache *cache, RefreshKeysJob *qq);
void doStart();
Error startKeyListing(GpgME::Protocol protocol);
void listAllKeysJobDone(const KeyListResult &res, const std::vector<Key> &nextKeys)
{
std::vector<Key> keys;
keys.reserve(m_keys.size() + nextKeys.size());
if (m_keys.empty()) {
keys = nextKeys;
} else {
std::merge(m_keys.begin(), m_keys.end(), nextKeys.begin(), nextKeys.end(), std::back_inserter(keys), _detail::ByFingerprint<std::less>());
}
m_keys.swap(keys);
jobDone(res);
}
void emitDone(const KeyListResult &result);
void updateKeyCache();
QPointer<KeyCache> m_cache;
QVector<QGpgME::ListAllKeysJob *> m_jobsPending;
std::vector<Key> m_keys;
KeyListResult m_mergedResult;
bool m_canceled;
private:
void jobDone(const KeyListResult &res);
};
KeyCache::RefreshKeysJob::Private::Private(KeyCache *cache, RefreshKeysJob *qq)
: q(qq)
, m_cache(cache)
, m_canceled(false)
{
Q_ASSERT(m_cache);
}
void KeyCache::RefreshKeysJob::Private::jobDone(const KeyListResult &result)
{
if (m_canceled) {
q->deleteLater();
return;
}
QObject *const sender = q->sender();
if (sender) {
sender->disconnect(q);
}
Q_ASSERT(m_jobsPending.size() > 0);
m_jobsPending.removeOne(qobject_cast<QGpgME::ListAllKeysJob *>(sender));
m_mergedResult.mergeWith(result);
if (m_jobsPending.size() > 0) {
return;
}
updateKeyCache();
emitDone(m_mergedResult);
}
void KeyCache::RefreshKeysJob::Private::emitDone(const KeyListResult &res)
{
q->deleteLater();
Q_EMIT q->done(res);
}
KeyCache::RefreshKeysJob::RefreshKeysJob(KeyCache *cache, QObject *parent)
: QObject(parent)
, d(new Private(cache, this))
{
}
KeyCache::RefreshKeysJob::~RefreshKeysJob()
{
delete d;
}
void KeyCache::RefreshKeysJob::start()
{
QTimer::singleShot(0, this, [this]() {
d->doStart();
});
}
void KeyCache::RefreshKeysJob::cancel()
{
d->m_canceled = true;
std::for_each(d->m_jobsPending.begin(), d->m_jobsPending.end(), std::mem_fn(&QGpgME::ListAllKeysJob::slotCancel));
Q_EMIT canceled();
}
void KeyCache::RefreshKeysJob::Private::doStart()
{
if (m_canceled) {
q->deleteLater();
return;
}
Q_ASSERT(m_jobsPending.size() == 0);
m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::OpenPGP)));
m_mergedResult.mergeWith(KeyListResult(startKeyListing(GpgME::CMS)));
if (m_jobsPending.size() != 0) {
return;
}
const bool hasError = m_mergedResult.error() || m_mergedResult.error().isCanceled();
emitDone(hasError ? m_mergedResult : KeyListResult(Error(GPG_ERR_UNSUPPORTED_OPERATION)));
}
void KeyCache::RefreshKeysJob::Private::updateKeyCache()
{
if (!m_cache || m_canceled) {
q->deleteLater();
return;
}
std::vector<Key> cachedKeys = m_cache->initialized() ? m_cache->keys() : std::vector<Key>();
std::sort(cachedKeys.begin(), cachedKeys.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> keysToRemove;
std::set_difference(cachedKeys.begin(),
cachedKeys.end(),
m_keys.begin(),
m_keys.end(),
std::back_inserter(keysToRemove),
_detail::ByFingerprint<std::less>());
m_cache->remove(keysToRemove);
m_cache->refresh(m_keys);
}
Error KeyCache::RefreshKeysJob::Private::startKeyListing(GpgME::Protocol proto)
{
const auto *const protocol = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
if (!protocol) {
return Error();
}
QGpgME::ListAllKeysJob *const job = protocol->listAllKeysJob(/*includeSigs*/ false, /*validate*/ true);
if (!job) {
return Error();
}
#if 0
aheinecke: 2017.01.12:
For unknown reasons the new style connect fails at runtime
over library borders into QGpgME from the GpgME repo
when cross compiled for Windows and default arguments
are used in the Signal.
This was tested with gcc 4.9 (Mingw 3.0.2) and we could not
find an explanation for this. So until this is fixed or we understand
the problem we need to use the old style connect for QGpgME signals.
The new style connect of the canceled signal right below
works fine.
connect(job, &QGpgME::ListAllKeysJob::result,
q, [this](const GpgME::KeyListResult &res, const std::vector<GpgME::Key> &keys) {
listAllKeysJobDone(res, keys);
});
#endif
connect(job, SIGNAL(result(GpgME::KeyListResult, std::vector<GpgME::Key>)), q, SLOT(listAllKeysJobDone(GpgME::KeyListResult, std::vector<GpgME::Key>)));
connect(q, &RefreshKeysJob::canceled, job, &QGpgME::Job::slotCancel);
// Only do this for initialized keycaches to avoid huge waits for
// signature notations during initial keylisting.
if (proto == GpgME::OpenPGP && m_cache->remarksEnabled() && m_cache->initialized()) {
auto ctx = QGpgME::Job::context(job);
if (ctx) {
ctx->addKeyListMode(KeyListMode::Signatures | KeyListMode::SignatureNotations);
}
}
const Error error = job->start(true);
if (!error && !error.isCanceled()) {
m_jobsPending.push_back(job);
}
return error;
}
bool KeyCache::initialized() const
{
return d->m_initalized;
}
void KeyCache::Private::ensureCachePopulated() const
{
if (!m_initalized) {
q->startKeyListing();
QEventLoop loop;
loop.connect(q, &KeyCache::keyListingDone, &loop, &QEventLoop::quit);
qCDebug(LIBKLEO_LOG) << "Waiting for keycache.";
loop.exec();
qCDebug(LIBKLEO_LOG) << "Keycache available.";
}
}
bool KeyCache::pgpOnly() const
{
return d->m_pgpOnly;
}
static bool keyIsOk(const Key &k)
{
return !k.isExpired() && !k.isRevoked() && !k.isInvalid() && !k.isDisabled();
}
static bool uidIsOk(const UserID &uid)
{
return keyIsOk(uid.parent()) && !uid.isRevoked() && !uid.isInvalid();
}
static bool subkeyIsOk(const Subkey &s)
{
return !s.isRevoked() && !s.isInvalid() && !s.isDisabled();
}
namespace
{
time_t creationTimeOfNewestSuitableSubKey(const Key &key, KeyCache::KeyUsage usage)
{
time_t creationTime = 0;
for (const Subkey &s : key.subkeys()) {
if (!subkeyIsOk(s)) {
continue;
}
if (usage == KeyCache::KeyUsage::Sign && !s.canSign()) {
continue;
}
if (usage == KeyCache::KeyUsage::Encrypt && !s.canEncrypt()) {
continue;
}
if (s.creationTime() > creationTime) {
creationTime = s.creationTime();
}
}
return creationTime;
}
struct BestMatch {
Key key;
UserID uid;
time_t creationTime = 0;
};
}
GpgME::Key KeyCache::findBestByMailBox(const char *addr, GpgME::Protocol proto, KeyUsage usage) const
{
d->ensureCachePopulated();
if (!addr) {
return {};
}
// support lookup of email addresses enclosed in angle brackets
QByteArray address(addr);
if (address[0] == '<' && address[address.size() - 1] == '>') {
address = address.mid(1, address.size() - 2);
}
address = address.toLower();
BestMatch best;
for (const Key &k : findByEMailAddress(address.constData())) {
if (proto != Protocol::UnknownProtocol && k.protocol() != proto) {
continue;
}
if (usage == KeyUsage::Encrypt && !k.canEncrypt()) {
continue;
}
if (usage == KeyUsage::Sign && (!k.canSign() || !k.hasSecret())) {
continue;
}
const time_t creationTime = creationTimeOfNewestSuitableSubKey(k, usage);
if (creationTime == 0) {
// key does not have a suitable (and usable) subkey
continue;
}
for (const UserID &u : k.userIDs()) {
if (QByteArray::fromStdString(u.addrSpec()).toLower() != address) {
// user ID does not match the given email address
continue;
}
if (best.uid.isNull()) {
// we have found our first candidate
best = {k, u, creationTime};
} else if (!uidIsOk(best.uid) && uidIsOk(u)) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (!k.isExpired() && best.uid.validity() < u.validity()) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (best.key.isExpired() && !k.isExpired()) {
// validity of the new key is better
best = {k, u, creationTime};
} else if (best.uid.validity() == u.validity() && uidIsOk(u) && best.creationTime < creationTime) {
// both keys/user IDs have same validity, but the new key is newer
best = {k, u, creationTime};
}
}
}
return best.key;
}
namespace
{
template<typename T>
bool allKeysAllowUsage(const T &keys, KeyCache::KeyUsage usage)
{
switch (usage) {
case KeyCache::KeyUsage::AnyUsage:
return true;
case KeyCache::KeyUsage::Sign:
return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canSign));
case KeyCache::KeyUsage::Encrypt:
return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canEncrypt));
case KeyCache::KeyUsage::Certify:
return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canCertify));
case KeyCache::KeyUsage::Authenticate:
return std::all_of(std::begin(keys), std::end(keys), std::mem_fn(&Key::canAuthenticate));
}
qCDebug(LIBKLEO_LOG) << __func__ << "called with invalid usage" << int(usage);
return false;
}
template<typename T>
bool allKeysHaveProtocol(const T &keys, Protocol protocol)
{
return std::all_of(std::begin(keys), std::end(keys), [protocol](const auto &key) {
return key.protocol() == protocol;
});
}
}
KeyGroup KeyCache::findGroup(const QString &name, Protocol protocol, KeyUsage usage) const
{
d->ensureCachePopulated();
Q_ASSERT(usage == KeyUsage::Sign || usage == KeyUsage::Encrypt);
for (const auto &group : std::as_const(d->m_groups)) {
if (group.name() == name) {
const KeyGroup::Keys &keys = group.keys();
if (allKeysAllowUsage(keys, usage) && (protocol == UnknownProtocol || allKeysHaveProtocol(keys, protocol))) {
return group;
}
}
}
return {};
}
std::vector<Key> KeyCache::getGroupKeys(const QString &groupName) const
{
std::vector<Key> result;
for (const KeyGroup &g : std::as_const(d->m_groups)) {
if (g.name() == groupName) {
const KeyGroup::Keys &keys = g.keys();
std::copy(keys.cbegin(), keys.cend(), std::back_inserter(result));
}
}
_detail::sort_by_fpr(result);
_detail::remove_duplicates_by_fpr(result);
return result;
}
void KeyCache::setKeys(const std::vector<GpgME::Key> &keys)
{
// disable regular key listing and cancel running key listing
setRefreshInterval(0);
cancelKeyListing();
clear();
insert(keys);
d->m_initalized = true;
Q_EMIT keyListingDone(KeyListResult());
}
void KeyCache::setGroups(const std::vector<KeyGroup> &groups)
{
Q_ASSERT(d->m_initalized && "Call setKeys() before setting groups");
d->m_groups = groups;
Q_EMIT keysMayHaveChanged();
}
#include "moc_keycache.cpp"
#include "moc_keycache_p.cpp"
diff --git a/src/ui/cryptoconfigentryreaderport.cpp b/src/ui/cryptoconfigentryreaderport.cpp
index 6eb359f9..26ff606f 100644
--- a/src/ui/cryptoconfigentryreaderport.cpp
+++ b/src/ui/cryptoconfigentryreaderport.cpp
@@ -1,73 +1,73 @@
/*
ui/cryptoconfigentryreaderport.cpp
This file is part of libkleopatra
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "cryptoconfigentryreaderport_p.h"
#include "cryptoconfigmodule.h"
#include "readerportselection.h"
#include "utils/scdaemon.h"
+#include <libkleo_debug.h>
+
#include <KLocalizedString>
#include <QGpgME/CryptoConfig>
#if __has_include(<QGpgME/Debug>)
#include <QGpgME/Debug>
#endif
#include <QGridLayout>
#include <QLabel>
#include <gpgme++/error.h>
-#include <libkleo_debug.h>
-
namespace Kleo
{
CryptoConfigEntryReaderPort::CryptoConfigEntryReaderPort(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *layout,
QWidget *parent)
: CryptoConfigEntryGUI{module, entry, entryName}
, mReaderPort{new ReaderPortSelection{parent}}
{
auto const label = new QLabel{i18nc("@label:listbox Reader for smart cards", "Reader to connect to"), parent};
label->setBuddy(mReaderPort);
if (entry->isReadOnly()) {
label->setEnabled(false);
mReaderPort->setEnabled(false);
} else {
connect(mReaderPort, &ReaderPortSelection::valueChanged, this, &CryptoConfigEntryReaderPort::slotChanged);
}
const int row = layout->rowCount();
layout->addWidget(label, row, 1);
layout->addWidget(mReaderPort, row, 2);
}
void CryptoConfigEntryReaderPort::doSave()
{
if (mEntry->isReadOnly()) {
return;
}
mEntry->setStringValue(mReaderPort->value());
}
void CryptoConfigEntryReaderPort::doLoad()
{
mReaderPort->setValue(mEntry->stringValue());
}
} // namespace Kleo
diff --git a/src/ui/cryptoconfigmodule.cpp b/src/ui/cryptoconfigmodule.cpp
index a2d02694..02714053 100644
--- a/src/ui/cryptoconfigmodule.cpp
+++ b/src/ui/cryptoconfigmodule.cpp
@@ -1,1039 +1,1039 @@
/*
cryptoconfigmodule.cpp
This file is part of kgpgcertmanager
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "cryptoconfigmodule.h"
#include "cryptoconfigentryreaderport_p.h"
#include "cryptoconfigmodule_p.h"
#include "directoryserviceswidget.h"
#include "filenamerequester.h"
#include "kleo/keyserverconfig.h"
#include "utils/formatting.h"
#include "utils/gnupg.h"
+#include <kleo_ui_debug.h>
+
#include <KLazyLocalizedString>
#include <KLineEdit>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <QGpgME/CryptoConfig>
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QLayout>
#include <QPushButton>
#include <QRegExp>
#include <QRegularExpression>
#include <QScreen>
#include <QScrollArea>
#include <QSpinBox>
#include <QStyle>
#include <QVBoxLayout>
#include <QWindow>
#include <gpgme.h>
#include <array>
#include <limits>
#include <memory>
#include <set>
-#include <kleo_ui_debug.h>
-
using namespace Kleo;
namespace
{
class ScrollArea : public QScrollArea
{
public:
explicit ScrollArea(QWidget *p)
: QScrollArea(p)
{
}
QSize sizeHint() const override
{
const QSize wsz = widget() ? widget()->sizeHint() : QSize();
return {wsz.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent), QScrollArea::sizeHint().height()};
}
};
}
inline QIcon loadIcon(const QString &s)
{
QString ss = s;
const static QRegularExpression reg(QRegularExpression(QLatin1String("[^a-zA-Z0-9_]")));
return QIcon::fromTheme(ss.replace(reg, QStringLiteral("-")));
}
static unsigned int num_components_with_options(const QGpgME::CryptoConfig *config)
{
if (!config) {
return 0;
}
const QStringList components = config->componentList();
unsigned int result = 0;
for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) {
if (const QGpgME::CryptoConfigComponent *const comp = config->component(*it)) {
if (!comp->groupList().empty()) {
++result;
}
}
}
return result;
}
static KPageView::FaceType determineJanusFace(const QGpgME::CryptoConfig *config, Kleo::CryptoConfigModule::Layout layout, bool &ok)
{
ok = true;
if (num_components_with_options(config) < 2) {
ok = false;
return KPageView::Plain;
}
switch (layout) {
case CryptoConfigModule::LinearizedLayout:
return KPageView::Plain;
case CryptoConfigModule::TabbedLayout:
return KPageView::Tabbed;
case CryptoConfigModule::IconListLayout:
return KPageView::List;
}
Q_ASSERT(!"we should never get here");
return KPageView::List;
}
Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, QWidget *parent)
: KPageWidget(parent)
, mConfig(config)
{
init(IconListLayout);
}
Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, Layout layout, QWidget *parent)
: KPageWidget(parent)
, mConfig(config)
{
init(layout);
}
void Kleo::CryptoConfigModule::init(Layout layout)
{
if (QLayout *l = this->layout()) {
l->setContentsMargins(0, 0, 0, 0);
}
QGpgME::CryptoConfig *const config = mConfig;
bool configOK = false;
const KPageView::FaceType type = determineJanusFace(config, layout, configOK);
setFaceType(type);
QVBoxLayout *vlay = nullptr;
QWidget *vbox = nullptr;
if (type == Plain) {
QWidget *w = new QWidget(this);
auto l = new QVBoxLayout(w);
l->setContentsMargins(0, 0, 0, 0);
auto s = new QScrollArea(w);
s->setFrameStyle(QFrame::NoFrame);
s->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
s->setWidgetResizable(true);
l->addWidget(s);
vbox = new QWidget(s->viewport());
vlay = new QVBoxLayout(vbox);
vlay->setContentsMargins(0, 0, 0, 0);
s->setWidget(vbox);
addPage(w, configOK ? QString() : i18n("GpgConf Error"));
}
const QStringList components = sortComponentList(config->componentList());
for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) {
// qCDebug(KLEO_UI_LOG) <<"Component" << (*it).toLocal8Bit() <<":";
QGpgME::CryptoConfigComponent *comp = config->component(*it);
Q_ASSERT(comp);
if (comp->groupList().empty()) {
continue;
}
std::unique_ptr<CryptoConfigComponentGUI> compGUI(new CryptoConfigComponentGUI(this, comp));
compGUI->setObjectName(*it);
// KJanusWidget doesn't seem to have iterators, so we store a copy...
mComponentGUIs.append(compGUI.get());
if (type == Plain) {
QGroupBox *gb = new QGroupBox(comp->description(), vbox);
(new QVBoxLayout(gb))->addWidget(compGUI.release());
vlay->addWidget(gb);
} else {
vbox = new QWidget(this);
vlay = new QVBoxLayout(vbox);
vlay->setContentsMargins(0, 0, 0, 0);
KPageWidgetItem *pageItem = new KPageWidgetItem(vbox, comp->description());
if (type != Tabbed) {
pageItem->setIcon(loadIcon(comp->iconName()));
}
addPage(pageItem);
QScrollArea *scrollArea = type == Tabbed ? new QScrollArea(vbox) : new ScrollArea(vbox);
scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
scrollArea->setWidgetResizable(true);
vlay->addWidget(scrollArea);
const QSize compGUISize = compGUI->sizeHint();
scrollArea->setWidget(compGUI.release());
// Set a nice startup size
const int deskHeight = screen()->size().height();
int dialogHeight;
if (deskHeight > 1000) { // very big desktop ?
dialogHeight = 800;
} else if (deskHeight > 650) { // big desktop ?
dialogHeight = 500;
} else { // small (800x600, 640x480) desktop
dialogHeight = 400;
}
Q_ASSERT(scrollArea->widget());
if (type != Tabbed) {
scrollArea->setMinimumHeight(qMin(compGUISize.height(), dialogHeight));
}
}
}
if (mComponentGUIs.empty()) {
const QString msg = i18n(
"The gpgconf tool used to provide the information "
"for this dialog does not seem to be installed "
"properly. It did not return any components. "
"Try running \"%1\" on the command line for more "
"information.",
components.empty() ? QLatin1String("gpgconf --list-components") : QLatin1String("gpgconf --list-options gpg"));
QLabel *label = new QLabel(msg, vbox);
label->setWordWrap(true);
label->setMinimumHeight(fontMetrics().lineSpacing() * 5);
vlay->addWidget(label);
}
}
namespace
{
template<typename Iterator>
QStringList sortConfigEntries(const Iterator orderBegin, const Iterator orderEnd, const QStringList &entries)
{
// components sorting algorithm:
// 1. components with predefined order (provided via orderBegin / orderEnd)
// 2. other components sorted alphabetically
QStringList result;
QStringList others;
for (auto it = orderBegin; it != orderEnd; ++it) {
if (entries.contains(*it)) {
result.append(*it);
}
}
for (const auto &item : entries) {
if (!result.contains(item)) {
others.append(item);
}
}
others.sort();
result.append(others);
return result;
}
} // namespace
QStringList Kleo::CryptoConfigModule::sortComponentList(const QStringList &components)
{
static const std::array<QString, 6> order = {
QStringLiteral("gpg"),
QStringLiteral("gpgsm"),
QStringLiteral("gpg-agent"),
QStringLiteral("dirmngr"),
QStringLiteral("pinentry"),
QStringLiteral("scdaemon"),
};
return sortConfigEntries(order.begin(), order.end(), components);
}
QStringList Kleo::CryptoConfigModule::sortGroupList(const QString &moduleName, const QStringList &groups)
{
if (moduleName == QStringLiteral("gpg")) {
static const std::array<QString, 4> order = {
QStringLiteral("Keyserver"),
QStringLiteral("Configuration"),
QStringLiteral("Monitor"),
QStringLiteral("Debug"),
};
return sortConfigEntries(order.begin(), order.end(), groups);
} else if (moduleName == QStringLiteral("gpgsm")) {
static const std::array<QString, 4> order = {
QStringLiteral("Security"),
QStringLiteral("Configuration"),
QStringLiteral("Monitor"),
QStringLiteral("Debug"),
};
return sortConfigEntries(order.begin(), order.end(), groups);
} else if (moduleName == QStringLiteral("gpg-agent")) {
static const std::array<QString, 5> order = {
QStringLiteral("Security"),
QStringLiteral("Passphrase policy"),
QStringLiteral("Configuration"),
QStringLiteral("Monitor"),
QStringLiteral("Debug"),
};
return sortConfigEntries(order.begin(), order.end(), groups);
} else if (moduleName == QStringLiteral("dirmngr")) {
static const std::array<QString, 10> order = {
QStringLiteral("Keyserver"),
QStringLiteral("HTTP"),
QStringLiteral("LDAP"),
QStringLiteral("OCSP"),
QStringLiteral("Tor"),
QStringLiteral("Enforcement"),
QStringLiteral("Configuration"),
QStringLiteral("Format"),
QStringLiteral("Monitor"),
QStringLiteral("Debug"),
};
return sortConfigEntries(order.begin(), order.end(), groups);
} else if (moduleName == QStringLiteral("scdaemon")) {
static const std::array<QString, 4> order = {
QStringLiteral("Monitor"),
QStringLiteral("Configuration"),
QStringLiteral("Security"),
QStringLiteral("Debug"),
};
return sortConfigEntries(order.begin(), order.end(), groups);
} else {
qCDebug(KLEO_UI_LOG) << "Configuration groups order is not defined for " << moduleName;
QStringList result(groups);
result.sort();
return result;
}
}
bool Kleo::CryptoConfigModule::hasError() const
{
return mComponentGUIs.empty();
}
void Kleo::CryptoConfigModule::save()
{
bool changed = false;
QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
for (; it != mComponentGUIs.end(); ++it) {
if ((*it)->save()) {
changed = true;
}
}
if (changed) {
mConfig->sync(true /*runtime*/);
}
}
void Kleo::CryptoConfigModule::reset()
{
QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
for (; it != mComponentGUIs.end(); ++it) {
(*it)->load();
}
}
void Kleo::CryptoConfigModule::defaults()
{
QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
for (; it != mComponentGUIs.end(); ++it) {
(*it)->defaults();
}
}
void Kleo::CryptoConfigModule::cancel()
{
mConfig->clear();
}
////
namespace
{
bool offerEntryForConfiguration(QGpgME::CryptoConfigEntry *entry)
{
static const QRegularExpression entryPathGroupSegmentRegexp{QStringLiteral("/.*/")};
static std::set<QString> entriesToExclude;
if (entriesToExclude.empty()) {
entriesToExclude.insert(QStringLiteral("gpg/keyserver"));
if (engineIsVersion(2, 3, 5, GpgME::GpgConfEngine)
|| (engineIsVersion(2, 2, 34, GpgME::GpgConfEngine) && !engineIsVersion(2, 3, 0, GpgME::GpgConfEngine))) {
// exclude for 2.2.{34,...} and 2.3.5+
entriesToExclude.insert(QStringLiteral("gpgsm/keyserver"));
}
}
const bool de_vs = Kleo::gnupgUsesDeVsCompliance();
// Skip "dangerous" expert options if we are running in CO_DE_VS.
// Otherwise, skip any options beyond "invisible" (== expert + 1) level.
const auto maxEntryLevel = de_vs ? QGpgME::CryptoConfigEntry::Level_Advanced //
: QGpgME::CryptoConfigEntry::Level_Expert + 1;
// we ignore the group when looking up entries to exclude because entries
// are uniquely identified by their name and their component
const auto entryId = entry->path().replace(entryPathGroupSegmentRegexp, QLatin1String{"/"}).toLower();
return (entry->level() <= maxEntryLevel) && (entriesToExclude.find(entryId) == entriesToExclude.end());
}
auto getGroupEntriesToOfferForConfiguration(QGpgME::CryptoConfigGroup *group)
{
std::vector<QGpgME::CryptoConfigEntry *> result;
const auto entryNames = group->entryList();
for (const auto &entryName : entryNames) {
auto *const entry = group->entry(entryName);
Q_ASSERT(entry);
if (offerEntryForConfiguration(entry)) {
result.push_back(entry);
} else {
qCDebug(KLEO_UI_LOG) << "entry" << entry->path() << "too advanced or excluded explicitly, skipping";
}
}
return result;
}
}
Kleo::CryptoConfigComponentGUI::CryptoConfigComponentGUI(CryptoConfigModule *module, QGpgME::CryptoConfigComponent *component, QWidget *parent)
: QWidget(parent)
, mComponent(component)
{
auto glay = new QGridLayout(this);
const QStringList groups = module->sortGroupList(mComponent->name(), mComponent->groupList());
if (groups.size() > 1) {
glay->setColumnMinimumWidth(0, 30);
for (QStringList::const_iterator it = groups.begin(), end = groups.end(); it != end; ++it) {
QGpgME::CryptoConfigGroup *group = mComponent->group(*it);
Q_ASSERT(group);
if (!group) {
continue;
}
auto groupEntries = getGroupEntriesToOfferForConfiguration(group);
if (groupEntries.size() == 0) {
// skip groups without entries to be offered in the UI
continue;
}
const QString title = group->description();
auto hbox = new QHBoxLayout;
hbox->addWidget(new QLabel{title.isEmpty() ? *it : title, this});
hbox->addWidget(new KSeparator{Qt::Horizontal, this}, 1);
const int row = glay->rowCount();
glay->addLayout(hbox, row, 0, 1, 3);
mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this));
}
} else if (!groups.empty()) {
auto *const group = mComponent->group(groups.front());
auto groupEntries = getGroupEntriesToOfferForConfiguration(group);
if (groupEntries.size() > 0) {
mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this));
}
}
glay->setRowStretch(glay->rowCount(), 1);
}
bool Kleo::CryptoConfigComponentGUI::save()
{
bool changed = false;
QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
for (; it != mGroupGUIs.end(); ++it) {
if ((*it)->save()) {
changed = true;
}
}
return changed;
}
void Kleo::CryptoConfigComponentGUI::load()
{
QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
for (; it != mGroupGUIs.end(); ++it) {
(*it)->load();
}
}
void Kleo::CryptoConfigComponentGUI::defaults()
{
QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
for (; it != mGroupGUIs.end(); ++it) {
(*it)->defaults();
}
}
////
Kleo::CryptoConfigGroupGUI::CryptoConfigGroupGUI(CryptoConfigModule *module,
QGpgME::CryptoConfigGroup *group,
const std::vector<QGpgME::CryptoConfigEntry *> &entries,
QGridLayout *glay,
QWidget *widget)
: QObject(module)
{
const int startRow = glay->rowCount();
for (auto entry : entries) {
CryptoConfigEntryGUI *entryGUI = CryptoConfigEntryGUIFactory::createEntryGUI(module, entry, entry->name(), glay, widget);
if (entryGUI) {
mEntryGUIs.append(entryGUI);
entryGUI->load();
}
}
const int endRow = glay->rowCount() - 1;
if (endRow < startRow) {
return;
}
const QString iconName = group->iconName();
if (iconName.isEmpty()) {
return;
}
QLabel *l = new QLabel(widget);
l->setPixmap(loadIcon(iconName).pixmap(32, 32));
glay->addWidget(l, startRow, 0, endRow - startRow + 1, 1, Qt::AlignTop);
}
bool Kleo::CryptoConfigGroupGUI::save()
{
bool changed = false;
QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
for (; it != mEntryGUIs.end(); ++it) {
if ((*it)->isChanged()) {
(*it)->save();
changed = true;
}
}
return changed;
}
void Kleo::CryptoConfigGroupGUI::load()
{
QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
for (; it != mEntryGUIs.end(); ++it) {
(*it)->load();
}
}
void Kleo::CryptoConfigGroupGUI::defaults()
{
QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
for (; it != mEntryGUIs.end(); ++it) {
(*it)->resetToDefault();
}
}
////
using constructor = CryptoConfigEntryGUI *(*)(CryptoConfigModule *, QGpgME::CryptoConfigEntry *, const QString &, QGridLayout *, QWidget *);
namespace
{
template<typename T_Widget>
CryptoConfigEntryGUI *_create(CryptoConfigModule *m, QGpgME::CryptoConfigEntry *e, const QString &n, QGridLayout *l, QWidget *p)
{
return new T_Widget(m, e, n, l, p);
}
}
static const struct WidgetsByEntryName {
const char *entryGlob;
constructor create;
} widgetsByEntryName[] = {
{"*/*/debug-level", &_create<CryptoConfigEntryDebugLevel>},
{"scdaemon/*/reader-port", &_create<CryptoConfigEntryReaderPort>},
};
static const unsigned int numWidgetsByEntryName = sizeof widgetsByEntryName / sizeof *widgetsByEntryName;
static const constructor listWidgets[QGpgME::CryptoConfigEntry::NumArgType] = {
// None: A list of options with no arguments (e.g. -v -v -v) is shown as a spinbox
&_create<CryptoConfigEntrySpinBox>,
nullptr, // String
// Int/UInt: Let people type list of numbers (1,2,3....). Untested.
&_create<CryptoConfigEntryLineEdit>,
&_create<CryptoConfigEntryLineEdit>,
nullptr, // Path
nullptr, // Formerly URL
&_create<CryptoConfigEntryLDAPURL>,
nullptr, // DirPath
};
static const constructor scalarWidgets[QGpgME::CryptoConfigEntry::NumArgType] = {
// clang-format off
&_create<CryptoConfigEntryCheckBox>, // None
&_create<CryptoConfigEntryLineEdit>, // String
&_create<CryptoConfigEntrySpinBox>, // Int
&_create<CryptoConfigEntrySpinBox>, // UInt
&_create<CryptoConfigEntryPath>, // Path
nullptr, // Formerly URL
nullptr, // LDAPURL
&_create<CryptoConfigEntryDirPath>, // DirPath
// clang-format on
};
CryptoConfigEntryGUI *Kleo::CryptoConfigEntryGUIFactory::createEntryGUI(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *glay,
QWidget *widget)
{
Q_ASSERT(entry);
// try to lookup by path:
const QString path = entry->path();
for (unsigned int i = 0; i < numWidgetsByEntryName; ++i) {
if (QRegExp(QLatin1String(widgetsByEntryName[i].entryGlob), Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(path)) {
return widgetsByEntryName[i].create(module, entry, entryName, glay, widget);
}
}
// none found, so look up by type:
const unsigned int argType = entry->argType();
Q_ASSERT(argType < QGpgME::CryptoConfigEntry::NumArgType);
if (entry->isList()) {
if (const constructor create = listWidgets[argType]) {
return create(module, entry, entryName, glay, widget);
} else {
qCWarning(KLEO_UI_LOG) << "No widget implemented for list of type" << entry->argType();
}
} else if (const constructor create = scalarWidgets[argType]) {
return create(module, entry, entryName, glay, widget);
} else {
qCWarning(KLEO_UI_LOG) << "No widget implemented for type" << entry->argType();
}
return nullptr;
}
////
Kleo::CryptoConfigEntryGUI::CryptoConfigEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName)
: QObject(module)
, mEntry(entry)
, mName(entryName)
, mChanged(false)
{
connect(this, &CryptoConfigEntryGUI::changed, module, &CryptoConfigModule::changed);
}
QString Kleo::CryptoConfigEntryGUI::description() const
{
QString descr = mEntry->description();
if (descr.isEmpty()) { // happens for expert options
// String does not need to be translated because the options itself
// are also not translated
return QStringLiteral("\"%1\"").arg(mName);
}
if (i18nc("Translate this to 'yes' or 'no' (use the English words!) "
"depending on whether your language uses "
"Sentence style capitalization in GUI labels (yes) or not (no). "
"Context: We get some backend strings in that have the wrong "
"capitalization (in English, at least) so we need to force the "
"first character to upper-case. It is this behaviour you can "
"control for your language with this translation.",
"yes")
== QLatin1String("yes")) {
descr[0] = descr[0].toUpper();
}
return descr;
}
void Kleo::CryptoConfigEntryGUI::resetToDefault()
{
mEntry->resetToDefault();
load();
}
////
Kleo::CryptoConfigEntryLineEdit::CryptoConfigEntryLineEdit(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *glay,
QWidget *widget)
: CryptoConfigEntryGUI(module, entry, entryName)
{
const int row = glay->rowCount();
mLineEdit = new KLineEdit(widget);
QLabel *label = new QLabel(description(), widget);
label->setBuddy(mLineEdit);
glay->addWidget(label, row, 1);
glay->addWidget(mLineEdit, row, 2);
if (entry->isReadOnly()) {
label->setEnabled(false);
mLineEdit->setEnabled(false);
} else {
connect(mLineEdit, &KLineEdit::textChanged, this, &CryptoConfigEntryLineEdit::slotChanged);
}
}
void Kleo::CryptoConfigEntryLineEdit::doSave()
{
mEntry->setStringValue(mLineEdit->text());
}
void Kleo::CryptoConfigEntryLineEdit::doLoad()
{
mLineEdit->setText(mEntry->stringValue());
}
////
/* Note: Do not use "guru" as debug level but use the value 10. The
former also enables the creation of hash dump files and thus leaves
traces of plaintext on the disk. */
static const struct {
const KLazyLocalizedString label;
const char *name;
} debugLevels[] = {
{kli18n("0 - None"), "none"},
{kli18n("1 - Basic"), "basic"},
{kli18n("2 - Verbose"), "advanced"},
{kli18n("3 - More Verbose"), "expert"},
{kli18n("4 - All"), "10"},
};
static const unsigned int numDebugLevels = sizeof debugLevels / sizeof *debugLevels;
Kleo::CryptoConfigEntryDebugLevel::CryptoConfigEntryDebugLevel(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *glay,
QWidget *widget)
: CryptoConfigEntryGUI(module, entry, entryName)
, mComboBox(new QComboBox(widget))
{
QLabel *label = new QLabel(i18n("Set the debugging level to"), widget);
label->setBuddy(mComboBox);
for (unsigned int i = 0; i < numDebugLevels; ++i) {
mComboBox->addItem(KLocalizedString(debugLevels[i].label).toString());
}
if (entry->isReadOnly()) {
label->setEnabled(false);
mComboBox->setEnabled(false);
} else {
connect(mComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &CryptoConfigEntryDebugLevel::slotChanged);
}
const int row = glay->rowCount();
glay->addWidget(label, row, 1);
glay->addWidget(mComboBox, row, 2);
}
void Kleo::CryptoConfigEntryDebugLevel::doSave()
{
const unsigned int idx = mComboBox->currentIndex();
if (idx < numDebugLevels) {
mEntry->setStringValue(QLatin1String(debugLevels[idx].name));
} else {
mEntry->setStringValue(QString());
}
}
void Kleo::CryptoConfigEntryDebugLevel::doLoad()
{
const QString str = mEntry->stringValue();
for (unsigned int i = 0; i < numDebugLevels; ++i) {
if (str == QLatin1String(debugLevels[i].name)) {
mComboBox->setCurrentIndex(i);
return;
}
}
mComboBox->setCurrentIndex(0);
}
////
Kleo::CryptoConfigEntryPath::CryptoConfigEntryPath(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *glay,
QWidget *widget)
: CryptoConfigEntryGUI(module, entry, entryName)
, mFileNameRequester(nullptr)
{
const int row = glay->rowCount();
mFileNameRequester = new FileNameRequester(widget);
mFileNameRequester->setExistingOnly(false);
mFileNameRequester->setFilter(QDir::Files);
QLabel *label = new QLabel(description(), widget);
label->setBuddy(mFileNameRequester);
glay->addWidget(label, row, 1);
glay->addWidget(mFileNameRequester, row, 2);
if (entry->isReadOnly()) {
label->setEnabled(false);
mFileNameRequester->setEnabled(false);
} else {
connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryPath::slotChanged);
}
}
void Kleo::CryptoConfigEntryPath::doSave()
{
mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName()));
}
void Kleo::CryptoConfigEntryPath::doLoad()
{
if (mEntry->urlValue().isLocalFile()) {
mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile());
} else {
mFileNameRequester->setFileName(mEntry->urlValue().toString());
}
}
////
Kleo::CryptoConfigEntryDirPath::CryptoConfigEntryDirPath(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *glay,
QWidget *widget)
: CryptoConfigEntryGUI(module, entry, entryName)
, mFileNameRequester(nullptr)
{
const int row = glay->rowCount();
mFileNameRequester = new FileNameRequester(widget);
mFileNameRequester->setExistingOnly(false);
mFileNameRequester->setFilter(QDir::Dirs);
QLabel *label = new QLabel(description(), widget);
label->setBuddy(mFileNameRequester);
glay->addWidget(label, row, 1);
glay->addWidget(mFileNameRequester, row, 2);
if (entry->isReadOnly()) {
label->setEnabled(false);
mFileNameRequester->setEnabled(false);
} else {
connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryDirPath::slotChanged);
}
}
void Kleo::CryptoConfigEntryDirPath::doSave()
{
mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName()));
}
void Kleo::CryptoConfigEntryDirPath::doLoad()
{
mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile());
}
////
Kleo::CryptoConfigEntrySpinBox::CryptoConfigEntrySpinBox(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *glay,
QWidget *widget)
: CryptoConfigEntryGUI(module, entry, entryName)
{
if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_None && entry->isList()) {
mKind = ListOfNone;
} else if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_UInt) {
mKind = UInt;
} else {
Q_ASSERT(entry->argType() == QGpgME::CryptoConfigEntry::ArgType_Int);
mKind = Int;
}
const int row = glay->rowCount();
mNumInput = new QSpinBox(widget);
QLabel *label = new QLabel(description(), widget);
label->setBuddy(mNumInput);
glay->addWidget(label, row, 1);
glay->addWidget(mNumInput, row, 2);
if (entry->isReadOnly()) {
label->setEnabled(false);
mNumInput->setEnabled(false);
} else {
mNumInput->setMinimum(mKind == Int ? std::numeric_limits<int>::min() : 0);
mNumInput->setMaximum(std::numeric_limits<int>::max());
connect(mNumInput, qOverload<int>(&QSpinBox::valueChanged), this, &CryptoConfigEntrySpinBox::slotChanged);
}
}
void Kleo::CryptoConfigEntrySpinBox::doSave()
{
int value = mNumInput->value();
switch (mKind) {
case ListOfNone:
mEntry->setNumberOfTimesSet(value);
break;
case UInt:
mEntry->setUIntValue(value);
break;
case Int:
mEntry->setIntValue(value);
break;
}
}
void Kleo::CryptoConfigEntrySpinBox::doLoad()
{
int value = 0;
switch (mKind) {
case ListOfNone:
value = mEntry->numberOfTimesSet();
break;
case UInt:
value = mEntry->uintValue();
break;
case Int:
value = mEntry->intValue();
break;
}
mNumInput->setValue(value);
}
////
Kleo::CryptoConfigEntryCheckBox::CryptoConfigEntryCheckBox(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *glay,
QWidget *widget)
: CryptoConfigEntryGUI(module, entry, entryName)
{
const int row = glay->rowCount();
mCheckBox = new QCheckBox(widget);
glay->addWidget(mCheckBox, row, 1, 1, 2);
mCheckBox->setText(description());
if (entry->isReadOnly()) {
mCheckBox->setEnabled(false);
} else {
connect(mCheckBox, &QCheckBox::toggled, this, &CryptoConfigEntryCheckBox::slotChanged);
}
}
void Kleo::CryptoConfigEntryCheckBox::doSave()
{
mEntry->setBoolValue(mCheckBox->isChecked());
}
void Kleo::CryptoConfigEntryCheckBox::doLoad()
{
mCheckBox->setChecked(mEntry->boolValue());
}
Kleo::CryptoConfigEntryLDAPURL::CryptoConfigEntryLDAPURL(CryptoConfigModule *module,
QGpgME::CryptoConfigEntry *entry,
const QString &entryName,
QGridLayout *glay,
QWidget *widget)
: CryptoConfigEntryGUI(module, entry, entryName)
{
mLabel = new QLabel(widget);
mPushButton = new QPushButton(entry->isReadOnly() ? i18n("Show...") : i18n("Edit..."), widget);
const int row = glay->rowCount();
QLabel *label = new QLabel(description(), widget);
label->setBuddy(mPushButton);
glay->addWidget(label, row, 1);
auto hlay = new QHBoxLayout;
glay->addLayout(hlay, row, 2);
hlay->addWidget(mLabel, 1);
hlay->addWidget(mPushButton);
if (entry->isReadOnly()) {
mLabel->setEnabled(false);
}
connect(mPushButton, &QPushButton::clicked, this, &CryptoConfigEntryLDAPURL::slotOpenDialog);
}
void Kleo::CryptoConfigEntryLDAPURL::doLoad()
{
setURLList(mEntry->urlValueList());
}
void Kleo::CryptoConfigEntryLDAPURL::doSave()
{
mEntry->setURLValueList(mURLList);
}
void prepareURLCfgDialog(QDialog *dialog, DirectoryServicesWidget *dirserv, bool readOnly)
{
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, dialog);
if (!readOnly) {
buttonBox->addButton(QDialogButtonBox::Cancel);
buttonBox->addButton(QDialogButtonBox::RestoreDefaults);
QPushButton *defaultsBtn = buttonBox->button(QDialogButtonBox::RestoreDefaults);
QObject::connect(defaultsBtn, &QPushButton::clicked, dirserv, &DirectoryServicesWidget::clear);
QObject::connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
}
QObject::connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
auto layout = new QVBoxLayout;
layout->addWidget(dirserv);
layout->addWidget(buttonBox);
dialog->setLayout(layout);
}
void Kleo::CryptoConfigEntryLDAPURL::slotOpenDialog()
{
if (!gpgme_check_version("1.16.0")) {
KMessageBox::sorry(mPushButton->parentWidget(),
i18n("Configuration of directory services is not possible "
"because the used gpgme libraries are too old."),
i18n("Sorry"));
return;
}
// I'm a bad boy and I do it all on the stack. Enough classes already :)
// This is just a simple dialog around the directory-services-widget
QDialog dialog(mPushButton->parentWidget());
dialog.setWindowTitle(i18nc("@title:window", "Configure Directory Services"));
auto dirserv = new DirectoryServicesWidget(&dialog);
prepareURLCfgDialog(&dialog, dirserv, mEntry->isReadOnly());
dirserv->setReadOnly(mEntry->isReadOnly());
std::vector<KeyserverConfig> servers;
std::transform(std::cbegin(mURLList), std::cend(mURLList), std::back_inserter(servers), [](const auto &url) {
return KeyserverConfig::fromUrl(url);
});
dirserv->setKeyservers(servers);
if (dialog.exec()) {
QList<QUrl> urls;
const auto servers = dirserv->keyservers();
std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), [](const auto &server) {
return server.toUrl();
});
setURLList(urls);
slotChanged();
}
}
void Kleo::CryptoConfigEntryLDAPURL::setURLList(const QList<QUrl> &urlList)
{
mURLList = urlList;
if (mURLList.isEmpty()) {
mLabel->setText(i18n("None configured"));
} else {
mLabel->setText(i18np("1 server configured", "%1 servers configured", mURLList.count()));
}
}
#include "moc_cryptoconfigmodule_p.cpp"
diff --git a/src/ui/directoryserviceswidget.cpp b/src/ui/directoryserviceswidget.cpp
index ffa8fb9c..8eb06253 100644
--- a/src/ui/directoryserviceswidget.cpp
+++ b/src/ui/directoryserviceswidget.cpp
@@ -1,410 +1,410 @@
/*
ui/directoryserviceswidget.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2017 Bundesamnt für Sicherheit in der Informationstechnik
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "directoryserviceswidget.h"
#include "editdirectoryservicedialog.h"
#include "kleo/keyserverconfig.h"
#include "utils/gnupg.h"
+#include <kleo_ui_debug.h>
+
#include <KLocalizedString>
#include <QInputDialog>
#include <QListView>
#include <QMenu>
#include <QPointer>
#include <QPushButton>
#include <QToolButton>
#include <QVBoxLayout>
-#include "kleo_ui_debug.h"
-
using namespace Kleo;
namespace
{
bool activeDirectoryIsSupported()
{
return engineIsVersion(2, 2, 28, GpgME::GpgSMEngine);
}
bool isStandardActiveDirectory(const KeyserverConfig &keyserver)
{
return (keyserver.authentication() == KeyserverAuthentication::ActiveDirectory) && keyserver.host().isEmpty();
}
bool keyserverIsEditable(const KeyserverConfig &keyserver)
{
// standard AD is not editable
return !isStandardActiveDirectory(keyserver);
}
class KeyserverModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit KeyserverModel(QObject *parent = nullptr)
: QAbstractListModel{parent}
{
}
void setKeyservers(const std::vector<KeyserverConfig> &servers)
{
clear();
beginInsertRows(QModelIndex(), 0, servers.size() - 1);
m_items = servers;
endInsertRows();
}
void addKeyserver(const KeyserverConfig &keyserver)
{
const auto row = m_items.size();
beginInsertRows(QModelIndex(), row, row);
m_items.push_back(keyserver);
endInsertRows();
}
KeyserverConfig getKeyserver(unsigned int id)
{
if (id >= m_items.size()) {
qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id;
return {};
}
return m_items[id];
}
void updateKeyserver(unsigned int id, const KeyserverConfig &keyserver)
{
if (id >= m_items.size()) {
qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id;
return;
}
m_items[id] = keyserver;
Q_EMIT dataChanged(index(id), index(id));
}
void deleteKeyserver(unsigned int id)
{
if (id >= m_items.size()) {
qCDebug(KLEO_UI_LOG) << __func__ << "invalid keyserver id:" << id;
return;
}
beginRemoveRows(QModelIndex(), id, id);
m_items.erase(m_items.begin() + id);
endRemoveRows();
}
void clear()
{
if (m_items.empty()) {
return;
}
beginRemoveRows(QModelIndex(), 0, m_items.size() - 1);
m_items.clear();
endRemoveRows();
}
int rowCount(const QModelIndex & = QModelIndex()) const override
{
return m_items.size();
}
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid()) {
return {};
}
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole: {
const auto keyserver = m_items[index.row()];
return isStandardActiveDirectory(keyserver) ? i18n("Active Directory") : keyserver.host();
}
}
return {};
}
bool hasActiveDirectory()
{
// check whether any of the model items represents an Active Directory keyserver
return std::any_of(std::cbegin(m_items), std::cend(m_items), isStandardActiveDirectory);
}
private:
using QAbstractListModel::setData;
private:
std::vector<KeyserverConfig> m_items;
};
}
class DirectoryServicesWidget::Private
{
DirectoryServicesWidget *const q;
struct {
QListView *keyserverList = nullptr;
QToolButton *newButton = nullptr;
QAction *addActiveDirectoryAction = nullptr;
QAction *addLdapServerAction = nullptr;
QPushButton *editButton = nullptr;
QPushButton *deleteButton = nullptr;
} ui;
KeyserverModel *keyserverModel = nullptr;
bool readOnly = false;
public:
Private(DirectoryServicesWidget *qq)
: q(qq)
{
auto mainLayout = new QVBoxLayout{q};
auto gridLayout = new QGridLayout{};
gridLayout->setColumnStretch(0, 1);
gridLayout->setRowStretch(1, 1);
keyserverModel = new KeyserverModel{q};
ui.keyserverList = new QListView();
ui.keyserverList->setModel(keyserverModel);
ui.keyserverList->setModelColumn(0);
ui.keyserverList->setSelectionBehavior(QAbstractItemView::SelectRows);
ui.keyserverList->setSelectionMode(QAbstractItemView::SingleSelection);
ui.keyserverList->setWhatsThis(i18nc("@info:whatsthis", "This is a list of all directory services that are configured for use with X.509."));
gridLayout->addWidget(ui.keyserverList, 1, 0);
auto groupsButtonLayout = new QVBoxLayout();
auto menu = new QMenu{q};
ui.addActiveDirectoryAction = menu->addAction(i18n("Active Directory"), [this]() {
addActiveDirectory();
});
ui.addActiveDirectoryAction->setToolTip(i18nc("@info:tooltip",
"Click to use a directory service running on your Active Directory. "
"This works only on Windows and requires GnuPG 2.2.28 or later."));
ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported());
ui.addLdapServerAction = menu->addAction(i18n("LDAP Server"), [this]() {
addLdapServer();
});
ui.addLdapServerAction->setToolTip(i18nc("@info:tooltip", "Click to add a directory service provided by an LDAP server."));
ui.newButton = new QToolButton{q};
ui.newButton->setText(i18n("Add"));
ui.newButton->setToolTip(i18nc("@info:tooltip", "Click to add a directory service."));
ui.newButton->setWhatsThis(i18nc("@info:whatsthis",
"Click this button to add a directory service to the list of services. "
"The change will only take effect once you acknowledge the configuration dialog."));
ui.newButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
ui.newButton->setPopupMode(QToolButton::InstantPopup);
ui.newButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); // expand horizontally like the QPushButtons
ui.newButton->setMenu(menu);
groupsButtonLayout->addWidget(ui.newButton);
ui.editButton = new QPushButton(i18n("Edit"));
ui.editButton->setToolTip(i18nc("@info:tooltip", "Click to edit the selected service."));
ui.editButton->setWhatsThis(i18nc("@info:whatsthis",
"Click this button to edit the settings of the currently selected directory service. "
"The changes will only take effect once you acknowledge the configuration dialog."));
ui.editButton->setEnabled(false);
groupsButtonLayout->addWidget(ui.editButton);
ui.deleteButton = new QPushButton(i18n("Delete"));
ui.deleteButton->setToolTip(i18nc("@info:tooltip", "Click to remove the selected service."));
ui.deleteButton->setWhatsThis(i18nc("@info:whatsthis",
"Click this button to remove the currently selected directory service. "
"The change will only take effect once you acknowledge the configuration dialog."));
ui.deleteButton->setEnabled(false);
groupsButtonLayout->addWidget(ui.deleteButton);
groupsButtonLayout->addStretch(1);
gridLayout->addLayout(groupsButtonLayout, 1, 1);
mainLayout->addLayout(gridLayout, /*stretch=*/1);
connect(keyserverModel, &QAbstractItemModel::dataChanged, q, [this]() {
modelChanged();
});
connect(keyserverModel, &QAbstractItemModel::rowsInserted, q, [this]() {
modelChanged();
});
connect(keyserverModel, &QAbstractItemModel::rowsRemoved, q, [this]() {
modelChanged();
});
connect(ui.keyserverList->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this]() {
selectionChanged();
});
connect(ui.keyserverList, &QListView::doubleClicked, q, [this](const QModelIndex &index) {
if (!readOnly) {
editKeyserver(index);
}
});
connect(ui.editButton, &QPushButton::clicked, q, [this]() {
editKeyserver();
});
connect(ui.deleteButton, &QPushButton::clicked, q, [this]() {
deleteKeyserver();
});
}
void setReadOnly(bool ro)
{
readOnly = ro;
updateActions();
}
void setKeyservers(const std::vector<KeyserverConfig> &servers)
{
keyserverModel->setKeyservers(servers);
}
std::vector<KeyserverConfig> keyservers() const
{
std::vector<KeyserverConfig> result;
result.reserve(keyserverModel->rowCount());
for (int row = 0; row < keyserverModel->rowCount(); ++row) {
result.push_back(keyserverModel->getKeyserver(row));
}
return result;
}
void clear()
{
if (keyserverModel->rowCount() == 0) {
return;
}
keyserverModel->clear();
}
private:
auto selectedIndex()
{
const auto indexes = ui.keyserverList->selectionModel()->selectedRows();
return indexes.empty() ? QModelIndex() : indexes[0];
}
void modelChanged()
{
updateActions();
Q_EMIT q->changed();
}
void selectionChanged()
{
updateActions();
}
void updateActions()
{
const auto index = selectedIndex();
ui.newButton->setEnabled(!readOnly);
ui.addActiveDirectoryAction->setEnabled(activeDirectoryIsSupported() && !keyserverModel->hasActiveDirectory());
ui.editButton->setEnabled(!readOnly && index.isValid() && keyserverIsEditable(keyserverModel->getKeyserver(index.row())));
ui.deleteButton->setEnabled(!readOnly && index.isValid());
}
void handleEditKeyserverDialogResult(const int id, const EditDirectoryServiceDialog *dialog)
{
if (id >= 0) {
keyserverModel->updateKeyserver(id, dialog->keyserver());
} else {
keyserverModel->addKeyserver(dialog->keyserver());
}
}
void showEditKeyserverDialog(const int id, const KeyserverConfig &keyserver, const QString &windowTitle)
{
QPointer<EditDirectoryServiceDialog> dialog{new EditDirectoryServiceDialog{q}};
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowModality(Qt::WindowModal);
dialog->setWindowTitle(windowTitle);
dialog->setKeyserver(keyserver);
connect(dialog, &QDialog::accepted, q, [dialog, id, this] {
handleEditKeyserverDialogResult(id, dialog);
});
dialog->show();
}
void addActiveDirectory()
{
KeyserverConfig keyserver;
keyserver.setAuthentication(KeyserverAuthentication::ActiveDirectory);
keyserverModel->addKeyserver(keyserver);
}
void addLdapServer()
{
showEditKeyserverDialog(-1, {}, i18nc("@title:window", "LDAP Directory Service"));
}
void editKeyserver(const QModelIndex &index = {})
{
const auto serverIndex = index.isValid() ? index : selectedIndex();
if (!serverIndex.isValid()) {
qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty";
return;
}
const auto id = serverIndex.row();
const KeyserverConfig keyserver = keyserverModel->getKeyserver(id);
if (!keyserverIsEditable(keyserver)) {
qCDebug(KLEO_UI_LOG) << __func__ << "selected keyserver (id:" << id << ") cannot be modified";
return;
}
showEditKeyserverDialog(id, keyserver, i18nc("@title:window", "LDAP Directory Service"));
}
void deleteKeyserver()
{
const QModelIndex serverIndex = selectedIndex();
if (!serverIndex.isValid()) {
qCDebug(KLEO_UI_LOG) << __func__ << "selection is empty";
return;
}
keyserverModel->deleteKeyserver(serverIndex.row());
}
};
DirectoryServicesWidget::DirectoryServicesWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
DirectoryServicesWidget::~DirectoryServicesWidget() = default;
void DirectoryServicesWidget::setKeyservers(const std::vector<KeyserverConfig> &servers)
{
d->setKeyservers(servers);
}
std::vector<KeyserverConfig> DirectoryServicesWidget::keyservers() const
{
return d->keyservers();
}
void DirectoryServicesWidget::setReadOnly(bool readOnly)
{
d->setReadOnly(readOnly);
}
void DirectoryServicesWidget::clear()
{
d->clear();
}
#include "directoryserviceswidget.moc"
diff --git a/src/ui/dnattributeorderconfigwidget.cpp b/src/ui/dnattributeorderconfigwidget.cpp
index 085e6e8a..33e1c2bf 100644
--- a/src/ui/dnattributeorderconfigwidget.cpp
+++ b/src/ui/dnattributeorderconfigwidget.cpp
@@ -1,340 +1,341 @@
/* -*- c++ -*-
dnattributeorderconfigwidget.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dnattributeorderconfigwidget.h"
#include "libkleo/dn.h"
-#include "libkleo_debug.h"
+
+#include <libkleo_debug.h>
#include <KLocalizedString>
#include <QIcon>
#include <QGridLayout>
#include <QLabel>
#include <QToolButton>
#include <KLazyLocalizedString>
#include <QHeaderView>
#include <QTreeWidget>
class Kleo::DNAttributeOrderConfigWidget::DNAttributeOrderConfigWidgetPrivate
{
public:
enum { UUp = 0, Up = 1, Left = 2, Right = 3, Down = 4, DDown = 5 };
#ifndef QT_NO_TREEWIDGET
QTreeWidget *availableLV = nullptr;
QTreeWidget *currentLV = nullptr;
#endif
QToolButton *navTB[6];
#ifndef QT_NO_TREEWIDGET
QTreeWidgetItem *placeHolderItem = nullptr;
#endif
};
#ifndef QT_NO_TREEWIDGET
static void prepare(QTreeWidget *lv)
{
lv->setAllColumnsShowFocus(true);
lv->header()->setStretchLastSection(true);
lv->setHeaderLabels(QStringList() << QString() << i18n("Description"));
}
#endif
Kleo::DNAttributeOrderConfigWidget::DNAttributeOrderConfigWidget(QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, f)
, d(new DNAttributeOrderConfigWidgetPrivate)
{
auto glay = new QGridLayout(this);
glay->setContentsMargins(0, 0, 0, 0);
glay->setColumnStretch(0, 1);
glay->setColumnStretch(2, 1);
int row = -1;
++row;
glay->addWidget(new QLabel(i18n("Available attributes:"), this), row, 0);
glay->addWidget(new QLabel(i18n("Current attribute order:"), this), row, 2);
++row;
glay->setRowStretch(row, 1);
#ifndef QT_NO_TREEWIDGET
d->availableLV = new QTreeWidget(this);
prepare(d->availableLV);
d->availableLV->sortItems(0, Qt::AscendingOrder);
glay->addWidget(d->availableLV, row, 0);
d->currentLV = new QTreeWidget(this);
prepare(d->currentLV);
glay->addWidget(d->currentLV, row, 2);
connect(d->availableLV, &QTreeWidget::itemClicked, this, &DNAttributeOrderConfigWidget::slotAvailableSelectionChanged);
connect(d->currentLV, &QTreeWidget::itemClicked, this, &DNAttributeOrderConfigWidget::slotCurrentOrderSelectionChanged);
d->placeHolderItem = new QTreeWidgetItem(d->availableLV);
d->placeHolderItem->setText(0, QStringLiteral("_X_"));
d->placeHolderItem->setText(1, i18n("All others"));
#endif
// the up/down/left/right arrow cross:
auto xlay = new QGridLayout();
xlay->setSpacing(0);
xlay->setObjectName(QStringLiteral("xlay"));
xlay->setAlignment(Qt::AlignCenter);
static const struct {
const char *icon;
int row, col;
const KLazyLocalizedString tooltip;
void (DNAttributeOrderConfigWidget::*slot)();
bool autorepeat;
} navButtons[] = {
{"go-top", 0, 1, kli18n("Move to top"), &DNAttributeOrderConfigWidget::slotDoubleUpButtonClicked, false},
{"go-up", 1, 1, kli18n("Move one up"), &DNAttributeOrderConfigWidget::slotUpButtonClicked, true},
{"go-previous", 2, 0, kli18n("Remove from current attribute order"), &DNAttributeOrderConfigWidget::slotLeftButtonClicked, false},
{"go-next", 2, 2, kli18n("Add to current attribute order"), &DNAttributeOrderConfigWidget::slotRightButtonClicked, false},
{"go-down", 3, 1, kli18n("Move one down"), &DNAttributeOrderConfigWidget::slotDownButtonClicked, true},
{"go-bottom", 4, 1, kli18n("Move to bottom"), &DNAttributeOrderConfigWidget::slotDoubleDownButtonClicked, false},
};
for (unsigned int i = 0; i < sizeof navButtons / sizeof *navButtons; ++i) {
QToolButton *tb = d->navTB[i] = new QToolButton(this);
tb->setIcon(QIcon::fromTheme(QLatin1String(navButtons[i].icon)));
tb->setEnabled(false);
tb->setToolTip(KLocalizedString(navButtons[i].tooltip).toString());
xlay->addWidget(tb, navButtons[i].row, navButtons[i].col);
tb->setAutoRepeat(navButtons[i].autorepeat);
connect(tb, &QToolButton::clicked, this, navButtons[i].slot);
}
glay->addLayout(xlay, row, 1);
}
Kleo::DNAttributeOrderConfigWidget::~DNAttributeOrderConfigWidget() = default;
void Kleo::DNAttributeOrderConfigWidget::setAttributeOrder(const QStringList &order)
{
#ifndef QT_NO_TREEWIDGET
// save the _X_ item:
takePlaceHolderItem();
// clear the rest:
d->availableLV->clear();
d->currentLV->clear();
// fill the RHS listview:
QTreeWidgetItem *last = nullptr;
for (QStringList::const_iterator it = order.begin(); it != order.end(); ++it) {
const QString attr = (*it).toUpper();
if (attr == QLatin1String("_X_")) {
takePlaceHolderItem();
d->currentLV->insertTopLevelItem(d->currentLV->topLevelItemCount(), d->placeHolderItem);
last = d->placeHolderItem;
} else {
last = new QTreeWidgetItem(d->currentLV, last);
last->setText(0, attr);
last->setText(1, DN::attributeNameToLabel(attr));
}
}
// fill the LHS listview with what's left:
const QStringList all = DN::attributeNames();
const QStringList::const_iterator end(all.end());
for (QStringList::const_iterator it = all.begin(); it != end; ++it) {
if (!order.contains(*it)) {
auto item = new QTreeWidgetItem(d->availableLV);
item->setText(0, *it);
item->setText(1, DN::attributeNameToLabel(*it));
}
}
if (!d->placeHolderItem->treeWidget()) {
d->availableLV->addTopLevelItem(d->placeHolderItem);
}
#endif
}
void Kleo::DNAttributeOrderConfigWidget::takePlaceHolderItem()
{
#ifndef QT_NO_TREEWIDGET
if (QTreeWidget *lv = d->placeHolderItem->treeWidget()) {
lv->takeTopLevelItem(lv->indexOfTopLevelItem(d->placeHolderItem));
}
#endif
}
QStringList Kleo::DNAttributeOrderConfigWidget::attributeOrder() const
{
QStringList order;
#ifndef QT_NO_TREEWIDGET
for (QTreeWidgetItemIterator it(d->currentLV); (*it); ++it) {
order.push_back((*it)->text(0));
}
#endif
return order;
}
void Kleo::DNAttributeOrderConfigWidget::slotAvailableSelectionChanged(QTreeWidgetItem *item)
{
d->navTB[DNAttributeOrderConfigWidgetPrivate::Right]->setEnabled(item);
}
void Kleo::DNAttributeOrderConfigWidget::slotCurrentOrderSelectionChanged(QTreeWidgetItem *item)
{
enableDisableButtons(item);
}
void Kleo::DNAttributeOrderConfigWidget::enableDisableButtons(QTreeWidgetItem *item)
{
#ifndef QT_NO_TREEWIDGET
d->navTB[DNAttributeOrderConfigWidgetPrivate::UUp]->setEnabled(item && d->currentLV->itemAbove(item));
d->navTB[DNAttributeOrderConfigWidgetPrivate::Up]->setEnabled(item && d->currentLV->itemAbove(item));
d->navTB[DNAttributeOrderConfigWidgetPrivate::Left]->setEnabled(item);
d->navTB[DNAttributeOrderConfigWidgetPrivate::Down]->setEnabled(item && d->currentLV->itemBelow(item));
d->navTB[DNAttributeOrderConfigWidgetPrivate::DDown]->setEnabled(item && d->currentLV->itemBelow(item));
#endif
}
void Kleo::DNAttributeOrderConfigWidget::slotUpButtonClicked()
{
#ifndef QT_NO_TREEWIDGET
if (d->currentLV->selectedItems().isEmpty()) {
return;
}
QTreeWidgetItem *item = d->currentLV->selectedItems().first();
int itemIndex = d->currentLV->indexOfTopLevelItem(item);
if (itemIndex <= 0) {
return;
}
d->currentLV->takeTopLevelItem(itemIndex);
d->currentLV->insertTopLevelItem(itemIndex - 1, item);
d->currentLV->clearSelection();
item->setSelected(true);
enableDisableButtons(item);
Q_EMIT changed();
#endif
}
void Kleo::DNAttributeOrderConfigWidget::slotDoubleUpButtonClicked()
{
#ifndef QT_NO_TREEWIDGET
if (d->currentLV->selectedItems().isEmpty()) {
return;
}
QTreeWidgetItem *item = d->currentLV->selectedItems().first();
int itemIndex = d->currentLV->indexOfTopLevelItem(item);
if (itemIndex == 0) {
return;
}
d->currentLV->takeTopLevelItem(itemIndex);
d->currentLV->insertTopLevelItem(0, item);
d->currentLV->clearSelection();
item->setSelected(true);
enableDisableButtons(item);
Q_EMIT changed();
#endif
}
void Kleo::DNAttributeOrderConfigWidget::slotDownButtonClicked()
{
#ifndef QT_NO_TREEWIDGET
if (d->currentLV->selectedItems().isEmpty()) {
return;
}
QTreeWidgetItem *item = d->currentLV->selectedItems().first();
int itemIndex = d->currentLV->indexOfTopLevelItem(item);
if (itemIndex + 1 >= d->currentLV->topLevelItemCount()) {
return;
}
d->currentLV->takeTopLevelItem(itemIndex);
d->currentLV->insertTopLevelItem(itemIndex + 1, item);
d->currentLV->clearSelection();
item->setSelected(true);
enableDisableButtons(item);
Q_EMIT changed();
#endif
}
void Kleo::DNAttributeOrderConfigWidget::slotDoubleDownButtonClicked()
{
#ifndef QT_NO_TREEWIDGET
if (d->currentLV->selectedItems().isEmpty()) {
return;
}
QTreeWidgetItem *item = d->currentLV->selectedItems().first();
const int itemIndex = d->currentLV->indexOfTopLevelItem(item);
if (itemIndex + 1 >= d->currentLV->topLevelItemCount()) {
return;
}
d->currentLV->takeTopLevelItem(itemIndex);
d->currentLV->addTopLevelItem(item);
d->currentLV->clearSelection();
item->setSelected(true);
enableDisableButtons(item);
Q_EMIT changed();
#endif
}
void Kleo::DNAttributeOrderConfigWidget::slotLeftButtonClicked()
{
#ifndef QT_NO_TREEWIDGET
if (d->currentLV->selectedItems().isEmpty()) {
return;
}
QTreeWidgetItem *right = d->currentLV->selectedItems().first();
QTreeWidgetItem *next = d->currentLV->itemBelow(right);
if (!next) {
next = d->currentLV->itemAbove(right);
}
d->currentLV->takeTopLevelItem(d->currentLV->indexOfTopLevelItem(right));
d->availableLV->addTopLevelItem(right);
d->availableLV->sortItems(0, Qt::AscendingOrder);
if (next) {
next->setSelected(true);
}
enableDisableButtons(next);
Q_EMIT changed();
#endif
}
void Kleo::DNAttributeOrderConfigWidget::slotRightButtonClicked()
{
#ifndef QT_NO_TREEWIDGET
if (d->availableLV->selectedItems().isEmpty()) {
return;
}
QTreeWidgetItem *left = d->availableLV->selectedItems().first();
QTreeWidgetItem *next = d->availableLV->itemBelow(left);
if (!next) {
next = d->availableLV->itemAbove(left);
}
d->availableLV->takeTopLevelItem(d->availableLV->indexOfTopLevelItem(left));
int newRightIndex = d->currentLV->topLevelItemCount();
if (!d->currentLV->selectedItems().isEmpty()) {
QTreeWidgetItem *right = d->currentLV->selectedItems().first();
newRightIndex = d->currentLV->indexOfTopLevelItem(right);
right->setSelected(false);
}
d->currentLV->insertTopLevelItem(newRightIndex, left);
left->setSelected(true);
enableDisableButtons(left);
d->navTB[DNAttributeOrderConfigWidgetPrivate::Right]->setEnabled(next);
if (next) {
next->setSelected(true);
}
Q_EMIT changed();
#endif
}
void Kleo::DNAttributeOrderConfigWidget::virtual_hook(int, void *)
{
}
diff --git a/src/ui/keylistview.cpp b/src/ui/keylistview.cpp
index 61bd73de..5c478313 100644
--- a/src/ui/keylistview.cpp
+++ b/src/ui/keylistview.cpp
@@ -1,556 +1,556 @@
/*
keylistview.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keylistview.h"
-#include "kleo_ui_debug.h"
+#include <kleo_ui_debug.h>
#include <QColor>
#include <QFont>
#include <QFontMetrics>
#include <QPoint>
#include <QTimer>
#include <QToolTip>
#include <gpgme++/key.h>
#include <map>
#include <vector>
#include <QKeyEvent>
static const int updateDelayMilliSecs = 500;
class Q_DECL_HIDDEN Kleo::KeyListView::KeyListViewPrivate
{
public:
KeyListViewPrivate()
: updateTimer(nullptr)
{
}
std::vector<GpgME::Key> keyBuffer;
QTimer *updateTimer = nullptr;
std::map<QByteArray, KeyListViewItem *> itemMap;
};
// a list of signals where we want to replace QListViewItem with
// Kleo:KeyListViewItem:
static const struct {
const char *source;
const char *target;
} signalReplacements[] = {
{
SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)),
SLOT(slotEmitDoubleClicked(QTreeWidgetItem *, int)),
},
{
SIGNAL(itemSelectionChanged()),
SLOT(slotEmitSelectionChanged()),
},
{
SIGNAL(customContextMenuRequested(QPoint)),
SLOT(slotEmitContextMenu(QPoint)),
},
};
static const int numSignalReplacements = sizeof signalReplacements / sizeof *signalReplacements;
Kleo::KeyListView::KeyListView(const ColumnStrategy *columnStrategy, const DisplayStrategy *displayStrategy, QWidget *parent, Qt::WindowFlags f)
: QTreeWidget(parent)
, mColumnStrategy(columnStrategy)
, mDisplayStrategy(displayStrategy)
, mHierarchical(false)
, d(new KeyListViewPrivate())
{
setWindowFlags(f);
setContextMenuPolicy(Qt::CustomContextMenu);
d->updateTimer = new QTimer(this);
d->updateTimer->setSingleShot(true);
connect(d->updateTimer, &QTimer::timeout, this, &KeyListView::slotUpdateTimeout);
if (!columnStrategy) {
qCWarning(KLEO_UI_LOG) << "Kleo::KeyListView: need a column strategy to work with!";
return;
}
const QFontMetrics fm = fontMetrics();
for (int col = 0; !columnStrategy->title(col).isEmpty(); ++col) {
headerItem()->setText(col, columnStrategy->title(col));
header()->resizeSection(col, columnStrategy->width(col, fm));
header()->setSectionResizeMode(col, columnStrategy->resizeMode(col));
}
setAllColumnsShowFocus(true);
for (int i = 0; i < numSignalReplacements; ++i) {
connect(this, signalReplacements[i].source, signalReplacements[i].target);
}
this->setToolTip(QString());
viewport()->setToolTip(QString()); // make double sure :)
}
Kleo::KeyListView::~KeyListView()
{
d->updateTimer->stop();
// need to clear here, since in ~QListView, our children won't have
// a valid listView() pointing to us anymore, and their dtors try to
// unregister from us.
clear();
Q_ASSERT(d->itemMap.size() == 0);
// need to delete the tooltip ourselves, as ~QToolTip isn't virtual :o
delete mColumnStrategy;
mColumnStrategy = nullptr;
delete mDisplayStrategy;
mDisplayStrategy = nullptr;
}
void Kleo::KeyListView::takeItem(QTreeWidgetItem *qlvi)
{
// qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListView::takeItem(" << qlvi <<" )";
if (auto *item = lvi_cast<KeyListViewItem>(qlvi)) {
deregisterItem(item);
}
takeTopLevelItem(indexOfTopLevelItem(qlvi));
}
void Kleo::KeyListView::setHierarchical(bool hier)
{
if (hier == mHierarchical) {
return;
}
mHierarchical = hier;
if (hier) {
gatherScattered();
} else {
scatterGathered(firstChild());
}
}
void Kleo::KeyListView::slotAddKey(const GpgME::Key &key)
{
if (key.isNull()) {
return;
}
d->keyBuffer.push_back(key);
if (!d->updateTimer->isActive()) {
d->updateTimer->start(updateDelayMilliSecs);
}
}
void Kleo::KeyListView::slotUpdateTimeout()
{
if (d->keyBuffer.empty()) {
return;
}
const bool wasUpdatesEnabled = viewport()->updatesEnabled();
if (wasUpdatesEnabled) {
viewport()->setUpdatesEnabled(false);
}
qCDebug(KLEO_UI_LOG) << "Kleo::KeyListView::slotUpdateTimeout(): processing" << d->keyBuffer.size() << "items en block";
if (hierarchical()) {
for (std::vector<GpgME::Key>::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) {
doHierarchicalInsert(*it);
}
gatherScattered();
} else {
for (std::vector<GpgME::Key>::const_iterator it = d->keyBuffer.begin(); it != d->keyBuffer.end(); ++it) {
(void)new KeyListViewItem(this, *it);
}
}
if (wasUpdatesEnabled) {
viewport()->setUpdatesEnabled(true);
}
d->keyBuffer.clear();
}
void Kleo::KeyListView::clear()
{
d->updateTimer->stop();
d->keyBuffer.clear();
while (QTreeWidgetItem *item = topLevelItem(0)) {
delete item;
}
QTreeWidget::clear();
}
void Kleo::KeyListView::registerItem(KeyListViewItem *item)
{
// qCDebug(KLEO_UI_LOG) <<"registerItem(" << item <<" )";
if (!item) {
return;
}
const QByteArray fpr = item->key().primaryFingerprint();
if (!fpr.isEmpty()) {
d->itemMap.insert(std::make_pair(fpr, item));
}
}
void Kleo::KeyListView::deregisterItem(const KeyListViewItem *item)
{
// qCDebug(KLEO_UI_LOG) <<"deregisterItem( KeyLVI:" << item <<" )";
if (!item) {
return;
}
auto it = d->itemMap.find(item->key().primaryFingerprint());
if (it == d->itemMap.end()) {
return;
}
// This Q_ASSERT triggers, though it shouldn't. Print some more
// information when it happens.
// Q_ASSERT( it->second == item );
if (it->second != item) {
qCWarning(KLEO_UI_LOG) << "deregisterItem:"
<< "item " << item->key().primaryFingerprint() //
<< "it->second" << (it->second ? it->second->key().primaryFingerprint() : "is null");
return;
}
d->itemMap.erase(it);
}
void Kleo::KeyListView::doHierarchicalInsert(const GpgME::Key &key)
{
const QByteArray fpr = key.primaryFingerprint();
if (fpr.isEmpty()) {
return;
}
KeyListViewItem *item = nullptr;
if (!key.isRoot()) {
if (KeyListViewItem *parent = itemByFingerprint(key.chainID())) {
item = new KeyListViewItem(parent, key);
parent->setExpanded(true);
}
}
if (!item) {
item = new KeyListViewItem(this, key); // top-level (for now)
}
d->itemMap.insert(std::make_pair(fpr, item));
}
void Kleo::KeyListView::gatherScattered()
{
KeyListViewItem *item = firstChild();
while (item) {
KeyListViewItem *cur = item;
item = item->nextSibling();
if (cur->key().isRoot()) {
continue;
}
if (KeyListViewItem *parent = itemByFingerprint(cur->key().chainID())) {
// found a new parent...
// ### todo: optimize by suppressing removing/adding the item to the itemMap...
takeTopLevelItem(indexOfTopLevelItem(cur));
parent->addChild(cur);
parent->setExpanded(true);
}
}
}
void Kleo::KeyListView::scatterGathered(KeyListViewItem *start)
{
KeyListViewItem *item = start;
while (item) {
KeyListViewItem *cur = item;
item = item->nextSibling();
scatterGathered(Kleo::lvi_cast<Kleo::KeyListViewItem>(cur->child(0)));
Q_ASSERT(cur->childCount() == 0);
// ### todo: optimize by suppressing removing/adding the item to the itemMap...
if (cur->parent()) {
static_cast<Kleo::KeyListViewItem *>(cur->parent())->takeItem(cur);
} else {
takeItem(cur);
}
addTopLevelItem(cur);
}
}
Kleo::KeyListViewItem *Kleo::KeyListView::itemByFingerprint(const QByteArray &s) const
{
if (s.isEmpty()) {
return nullptr;
}
const std::map<QByteArray, KeyListViewItem *>::const_iterator it = d->itemMap.find(s);
if (it == d->itemMap.end()) {
return nullptr;
}
return it->second;
}
void Kleo::KeyListView::slotRefreshKey(const GpgME::Key &key)
{
const char *fpr = key.primaryFingerprint();
if (!fpr) {
return;
}
if (KeyListViewItem *item = itemByFingerprint(fpr)) {
item->setKey(key);
} else {
// none found -> add it
slotAddKey(key);
}
}
// slots for the emission of covariant Q_SIGNALS:
void Kleo::KeyListView::slotEmitDoubleClicked(QTreeWidgetItem *item, int col)
{
if (!item || lvi_cast<KeyListViewItem>(item)) {
Q_EMIT doubleClicked(static_cast<KeyListViewItem *>(item), col);
}
}
void Kleo::KeyListView::slotEmitReturnPressed(QTreeWidgetItem *item)
{
if (!item || lvi_cast<KeyListViewItem>(item)) {
Q_EMIT returnPressed(static_cast<KeyListViewItem *>(item));
}
}
void Kleo::KeyListView::slotEmitSelectionChanged()
{
Q_EMIT selectionChanged(selectedItem());
}
void Kleo::KeyListView::slotEmitContextMenu(const QPoint &pos)
{
QTreeWidgetItem *item = itemAt(pos);
if (!item || lvi_cast<KeyListViewItem>(item)) {
Q_EMIT contextMenu(static_cast<KeyListViewItem *>(item), viewport()->mapToGlobal(pos));
}
}
//
//
// KeyListViewItem
//
//
Kleo::KeyListViewItem::KeyListViewItem(KeyListView *parent, const GpgME::Key &key)
: QTreeWidgetItem(parent, RTTI)
{
Q_ASSERT(parent);
setKey(key);
}
Kleo::KeyListViewItem::KeyListViewItem(KeyListView *parent, KeyListViewItem *after, const GpgME::Key &key)
: QTreeWidgetItem(parent, after, RTTI)
{
Q_ASSERT(parent);
setKey(key);
}
Kleo::KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, const GpgME::Key &key)
: QTreeWidgetItem(parent, RTTI)
{
Q_ASSERT(parent && parent->listView());
setKey(key);
}
Kleo::KeyListViewItem::KeyListViewItem(KeyListViewItem *parent, KeyListViewItem *after, const GpgME::Key &key)
: QTreeWidgetItem(parent, after, RTTI)
{
Q_ASSERT(parent && parent->listView());
setKey(key);
}
Kleo::KeyListViewItem::~KeyListViewItem()
{
// delete the children first... When children are deleted in the
// QLVI dtor, they don't have listView() anymore, thus they don't
// call deregister( this ), leading to stale entries in the
// itemMap...
while (QTreeWidgetItem *item = child(0)) {
delete item;
}
// better do this here, too, since deletion is top-down and thus
// we're deleted when our parent item is no longer a
// KeyListViewItem, but a mere QListViewItem, so our takeItem()
// overload is gone by that time...
if (KeyListView *lv = listView()) {
lv->deregisterItem(this);
}
}
void Kleo::KeyListViewItem::setKey(const GpgME::Key &key)
{
KeyListView *lv = listView();
if (lv) {
lv->deregisterItem(this);
}
mKey = key;
if (lv) {
lv->registerItem(this);
}
// the ColumnStrategy operations might be very slow, so cache their
// result here, where we're non-const :)
const Kleo::KeyListView::ColumnStrategy *cs = lv ? lv->columnStrategy() : nullptr;
if (!cs) {
return;
}
const KeyListView::DisplayStrategy *ds = lv->displayStrategy();
const int numCols = lv ? lv->columnCount() : 0;
for (int i = 0; i < numCols; ++i) {
setText(i, cs->text(key, i));
setToolTip(i, cs->toolTip(key, i));
const QIcon icon = cs->icon(key, i);
if (!icon.isNull()) {
setIcon(i, icon);
}
if (ds) {
setForeground(i, QBrush(ds->keyForeground(key, foreground(i).color())));
setBackground(i, QBrush(ds->keyBackground(key, background(i).color())));
setFont(i, ds->keyFont(key, font(i)));
}
}
}
QString Kleo::KeyListViewItem::toolTip(int col) const
{
return listView() && listView()->columnStrategy() ? listView()->columnStrategy()->toolTip(key(), col) : QString();
}
bool Kleo::KeyListViewItem::operator<(const QTreeWidgetItem &other) const
{
if (other.type() != RTTI || !listView() || !listView()->columnStrategy()) {
return QTreeWidgetItem::operator<(other);
}
const auto that = static_cast<const KeyListViewItem *>(&other);
return listView()->columnStrategy()->compare(this->key(), that->key(), treeWidget()->sortColumn()) < 0;
}
void Kleo::KeyListViewItem::takeItem(QTreeWidgetItem *qlvi)
{
// qCDebug(KLEO_UI_LOG) <<"Kleo::KeyListViewItem::takeItem(" << qlvi <<" )";
if (auto *item = lvi_cast<KeyListViewItem>(qlvi)) {
listView()->deregisterItem(item);
}
takeChild(indexOfChild(qlvi));
}
//
//
// ColumnStrategy
//
//
Kleo::KeyListView::ColumnStrategy::~ColumnStrategy()
{
}
int Kleo::KeyListView::ColumnStrategy::compare(const GpgME::Key &key1, const GpgME::Key &key2, const int col) const
{
return QString::localeAwareCompare(text(key1, col), text(key2, col));
}
int Kleo::KeyListView::ColumnStrategy::width(int col, const QFontMetrics &fm) const
{
return fm.horizontalAdvance(title(col)) * 2;
}
QString Kleo::KeyListView::ColumnStrategy::toolTip(const GpgME::Key &key, int col) const
{
return text(key, col);
}
//
//
// DisplayStrategy
//
//
Kleo::KeyListView::DisplayStrategy::~DisplayStrategy()
{
}
// font
QFont Kleo::KeyListView::DisplayStrategy::keyFont(const GpgME::Key &, const QFont &font) const
{
return font;
}
// foreground
QColor Kleo::KeyListView::DisplayStrategy::keyForeground(const GpgME::Key &, const QColor &fg) const
{
return fg;
}
// background
QColor Kleo::KeyListView::DisplayStrategy::keyBackground(const GpgME::Key &, const QColor &bg) const
{
return bg;
}
//
//
// Collection of covariant return reimplementations of QListView(Item)
// members:
//
//
Kleo::KeyListView *Kleo::KeyListViewItem::listView() const
{
return static_cast<Kleo::KeyListView *>(QTreeWidgetItem::treeWidget());
}
Kleo::KeyListViewItem *Kleo::KeyListViewItem::nextSibling() const
{
if (parent()) {
const int myIndex = parent()->indexOfChild(const_cast<KeyListViewItem *>(this));
return static_cast<Kleo::KeyListViewItem *>(parent()->child(myIndex + 1));
}
const int myIndex = treeWidget()->indexOfTopLevelItem(const_cast<KeyListViewItem *>(this));
return static_cast<Kleo::KeyListViewItem *>(treeWidget()->topLevelItem(myIndex + 1));
}
Kleo::KeyListViewItem *Kleo::KeyListView::firstChild() const
{
return static_cast<Kleo::KeyListViewItem *>(topLevelItem(0));
}
Kleo::KeyListViewItem *Kleo::KeyListView::selectedItem() const
{
QList<KeyListViewItem *> selection = selectedItems();
if (selection.isEmpty()) {
return nullptr;
}
return selection.first();
}
QList<Kleo::KeyListViewItem *> Kleo::KeyListView::selectedItems() const
{
QList<KeyListViewItem *> result;
const auto selectedItems = QTreeWidget::selectedItems();
for (QTreeWidgetItem *selectedItem : selectedItems) {
if (auto *i = Kleo::lvi_cast<Kleo::KeyListViewItem>(selectedItem)) {
result.append(i);
}
}
return result;
}
bool Kleo::KeyListView::isMultiSelection() const
{
return selectionMode() == ExtendedSelection || selectionMode() == MultiSelection;
}
void Kleo::KeyListView::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
if (selectedItem()) {
slotEmitReturnPressed(selectedItem());
}
}
QTreeView::keyPressEvent(event);
}
diff --git a/src/ui/keyselectioncombo.cpp b/src/ui/keyselectioncombo.cpp
index d3c31721..2525e485 100644
--- a/src/ui/keyselectioncombo.cpp
+++ b/src/ui/keyselectioncombo.cpp
@@ -1,651 +1,652 @@
/* This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keyselectioncombo.h"
-#include <kleo_ui_debug.h>
#include "kleo/defaultkeyfilter.h"
#include "kleo/dn.h"
#include "models/keycache.h"
#include "models/keylist.h"
#include "models/keylistmodel.h"
#include "models/keylistsortfilterproxymodel.h"
#include "progressbar.h"
#include "utils/formatting.h"
+#include <kleo_ui_debug.h>
+
#include <gpgme++/key.h>
#include <QSortFilterProxyModel>
#include <QTimer>
#include <QVector>
#include <KLocalizedString>
using namespace Kleo;
Q_DECLARE_METATYPE(GpgME::Key)
namespace
{
class SortFilterProxyModel : public KeyListSortFilterProxyModel
{
Q_OBJECT
public:
using KeyListSortFilterProxyModel::KeyListSortFilterProxyModel;
void setAlwaysAcceptedKey(const QString &fingerprint)
{
if (fingerprint == mFingerprint) {
return;
}
mFingerprint = fingerprint;
invalidate();
}
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
if (!mFingerprint.isEmpty()) {
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
const auto fingerprint = sourceModel()->data(index, KeyList::FingerprintRole).toString();
if (fingerprint == mFingerprint) {
return true;
}
}
return KeyListSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
private:
QString mFingerprint;
};
class ProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
private:
struct CustomItem {
QIcon icon;
QString text;
QVariant data;
QString toolTip;
};
public:
ProxyModel(QObject *parent = nullptr)
: QSortFilterProxyModel(parent)
{
}
~ProxyModel() override
{
qDeleteAll(mFrontItems);
qDeleteAll(mBackItems);
}
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
{
const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value<GpgME::Key>();
const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value<GpgME::Key>();
if (leftKey.isNull()) {
return false;
}
if (rightKey.isNull()) {
return true;
}
// As we display UID(0) this is ok. We probably need a get Best UID at some point.
const auto lUid = leftKey.userID(0);
const auto rUid = rightKey.userID(0);
if (lUid.isNull()) {
return false;
}
if (rUid.isNull()) {
return true;
}
int cmp = strcmp(lUid.id(), rUid.id());
if (cmp) {
return cmp < 0;
}
if (lUid.validity() == rUid.validity()) {
/* Both are the same check which one is newer. */
time_t oldTime = 0;
for (const GpgME::Subkey &s : leftKey.subkeys()) {
if (s.isRevoked() || s.isInvalid() || s.isDisabled()) {
continue;
}
if (s.creationTime() > oldTime) {
oldTime = s.creationTime();
}
}
time_t newTime = 0;
for (const GpgME::Subkey &s : rightKey.subkeys()) {
if (s.isRevoked() || s.isInvalid() || s.isDisabled()) {
continue;
}
if (s.creationTime() > newTime) {
newTime = s.creationTime();
}
}
return newTime < oldTime;
}
return lUid.validity() > rUid.validity();
}
bool isCustomItem(const int row) const
{
return row < mFrontItems.count() || row >= mFrontItems.count() + QSortFilterProxyModel::rowCount();
}
void prependItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
beginInsertRows(QModelIndex(), 0, 0);
mFrontItems.push_front(new CustomItem{icon, text, data, toolTip});
endInsertRows();
}
void appendItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
mBackItems.push_back(new CustomItem{icon, text, data, toolTip});
endInsertRows();
}
void removeCustomItem(const QVariant &data)
{
for (int i = 0; i < mFrontItems.count(); ++i) {
if (mFrontItems[i]->data == data) {
beginRemoveRows(QModelIndex(), i, i);
delete mFrontItems.takeAt(i);
endRemoveRows();
return;
}
}
for (int i = 0; i < mBackItems.count(); ++i) {
if (mBackItems[i]->data == data) {
const int index = mFrontItems.count() + QSortFilterProxyModel::rowCount() + i;
beginRemoveRows(QModelIndex(), index, index);
delete mBackItems.takeAt(i);
endRemoveRows();
return;
}
}
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
return mFrontItems.count() + QSortFilterProxyModel::rowCount(parent) + mBackItems.count();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
// pretend that there is only one column to workaround a bug in
// QAccessibleTable which provides the accessibility interface for the
// pop-up of the combo box
return 1;
}
QModelIndex mapToSource(const QModelIndex &index) const override
{
if (!isCustomItem(index.row())) {
const int row = index.row() - mFrontItems.count();
return sourceModel()->index(row, index.column());
} else {
return {};
}
}
QModelIndex mapFromSource(const QModelIndex &source_index) const override
{
const QModelIndex idx = QSortFilterProxyModel::mapFromSource(source_index);
return createIndex(mFrontItems.count() + idx.row(), idx.column(), idx.internalPointer());
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override
{
if (row < 0 || row >= rowCount()) {
return {};
}
if (row < mFrontItems.count()) {
return createIndex(row, column, mFrontItems[row]);
} else if (row >= mFrontItems.count() + QSortFilterProxyModel::rowCount()) {
return createIndex(row, column, mBackItems[row - mFrontItems.count() - QSortFilterProxyModel::rowCount()]);
} else {
const QModelIndex mi = QSortFilterProxyModel::index(row - mFrontItems.count(), column, parent);
return createIndex(row, column, mi.internalPointer());
}
}
Qt::ItemFlags flags(const QModelIndex &index) const override
{
Q_UNUSED(index)
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemNeverHasChildren;
}
QModelIndex parent(const QModelIndex &) const override
{
// Flat list
return {};
}
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid()) {
return QVariant();
}
if (isCustomItem(index.row())) {
Q_ASSERT(!mFrontItems.isEmpty() || !mBackItems.isEmpty());
auto ci = static_cast<CustomItem *>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
return ci->text;
case Qt::DecorationRole:
return ci->icon;
case Qt::UserRole:
return ci->data;
case Qt::ToolTipRole:
return ci->toolTip;
default:
return QVariant();
}
}
const auto key = QSortFilterProxyModel::data(index, KeyList::KeyRole).value<GpgME::Key>();
Q_ASSERT(!key.isNull());
if (key.isNull()) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole: {
const auto userID = key.userID(0);
QString name;
QString email;
if (key.protocol() == GpgME::OpenPGP) {
name = QString::fromUtf8(userID.name());
email = QString::fromUtf8(userID.email());
} else {
const Kleo::DN dn(userID.id());
name = dn[QStringLiteral("CN")];
email = dn[QStringLiteral("EMAIL")];
}
const auto nameAndEmail = email.isEmpty() ? name : name.isEmpty() ? email : i18nc("Name <email>", "%1 <%2>", name, email);
if (Kleo::KeyCache::instance()->pgpOnly()) {
return i18nc("Name <email> (validity, created: date)",
"%1 (%2, created: %3)",
nameAndEmail,
Kleo::Formatting::complianceStringShort(key),
Kleo::Formatting::creationDateString(key));
} else {
return i18nc("Name <email> (validity, type, created: date)",
"%1 (%2, %3, created: %4)",
nameAndEmail,
Kleo::Formatting::complianceStringShort(key),
Formatting::displayName(key.protocol()),
Kleo::Formatting::creationDateString(key));
}
}
case Qt::ToolTipRole: {
using namespace Kleo::Formatting;
return Kleo::Formatting::toolTip(key, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs);
}
case Qt::DecorationRole:
return Kleo::Formatting::iconForUid(key.userID(0));
default:
return QSortFilterProxyModel::data(index, role);
}
}
private:
QVector<CustomItem *> mFrontItems;
QVector<CustomItem *> mBackItems;
};
} // anonymous namespace
namespace Kleo
{
class KeySelectionComboPrivate
{
public:
KeySelectionComboPrivate(KeySelectionCombo *parent)
: wasEnabled(true)
, q(parent)
{
}
/* Selects the first key with a UID addrSpec that matches
* the mPerfectMatchMbox variable.
*
* The idea here is that if there are keys like:
*
* tom-store@abc.com
* susi-store@abc.com
* store@abc.com
*
* And the user wants to send a mail to "store@abc.com"
* the filter should still show tom and susi (because they
* both are part of store) but the key for "store" should
* be preselected.
*
* Returns true if one was selected. False otherwise. */
bool selectPerfectIdMatch() const
{
if (mPerfectMatchMbox.isEmpty()) {
return false;
}
for (int i = 0; i < proxyModel->rowCount(); ++i) {
const auto idx = proxyModel->index(i, 0, QModelIndex());
const auto key = proxyModel->data(idx, KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
// WTF?
continue;
}
for (const auto &uid : key.userIDs()) {
if (QString::fromStdString(uid.addrSpec()) == mPerfectMatchMbox) {
q->setCurrentIndex(i);
return true;
}
}
}
return false;
}
/* Updates the current key with the default key if the key matches
* the current key filter. */
void updateWithDefaultKey()
{
GpgME::Protocol filterProto = GpgME::UnknownProtocol;
const auto filter = dynamic_cast<const DefaultKeyFilter *>(sortFilterProxy->keyFilter().get());
if (filter && filter->isOpenPGP() == DefaultKeyFilter::Set) {
filterProto = GpgME::OpenPGP;
} else if (filter && filter->isOpenPGP() == DefaultKeyFilter::NotSet) {
filterProto = GpgME::CMS;
}
QString defaultKey = defaultKeys.value(filterProto);
if (defaultKey.isEmpty()) {
// Fallback to unknown protocol
defaultKey = defaultKeys.value(GpgME::UnknownProtocol);
}
// make sure that the default key is not filtered out unless it has the wrong protocol
if (filterProto == GpgME::UnknownProtocol) {
sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
} else {
const auto key = KeyCache::instance()->findByFingerprint(defaultKey.toLatin1().constData());
if (!key.isNull() && key.protocol() == filterProto) {
sortFilterProxy->setAlwaysAcceptedKey(defaultKey);
} else {
sortFilterProxy->setAlwaysAcceptedKey({});
}
}
q->setCurrentKey(defaultKey);
}
void storeCurrentSelectionBeforeModelChange()
{
keyBeforeModelChange = q->currentKey();
customItemBeforeModelChange = q->currentData();
}
void restoreCurrentSelectionAfterModelChange()
{
if (!keyBeforeModelChange.isNull()) {
q->setCurrentKey(keyBeforeModelChange);
} else if (customItemBeforeModelChange.isValid()) {
const auto index = q->findData(customItemBeforeModelChange);
if (index != -1) {
q->setCurrentIndex(index);
} else {
updateWithDefaultKey();
}
}
}
Kleo::AbstractKeyListModel *model = nullptr;
SortFilterProxyModel *sortFilterProxy = nullptr;
ProxyModel *proxyModel = nullptr;
std::shared_ptr<Kleo::KeyCache> cache;
QMap<GpgME::Protocol, QString> defaultKeys;
bool wasEnabled = false;
bool useWasEnabled = false;
bool secretOnly = false;
bool initialKeyListingDone = false;
QString mPerfectMatchMbox;
GpgME::Key keyBeforeModelChange;
QVariant customItemBeforeModelChange;
private:
KeySelectionCombo *const q;
};
}
using namespace Kleo;
KeySelectionCombo::KeySelectionCombo(QWidget *parent)
: KeySelectionCombo(true, parent)
{
}
KeySelectionCombo::KeySelectionCombo(bool secretOnly, QWidget *parent)
: QComboBox(parent)
, d(new KeySelectionComboPrivate(this))
{
d->model = Kleo::AbstractKeyListModel::createFlatKeyListModel(this);
d->secretOnly = secretOnly;
d->sortFilterProxy = new SortFilterProxyModel(this);
d->sortFilterProxy->setSourceModel(d->model);
d->proxyModel = new ProxyModel(this);
d->proxyModel->setSourceModel(d->sortFilterProxy);
setModel(d->proxyModel);
connect(this, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int row) {
if (row >= 0 && row < d->proxyModel->rowCount()) {
if (d->proxyModel->isCustomItem(row)) {
Q_EMIT customItemSelected(currentData(Qt::UserRole));
} else {
Q_EMIT currentKeyChanged(currentKey());
}
}
});
d->cache = Kleo::KeyCache::mutableInstance();
connect(model(), &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() {
d->storeCurrentSelectionBeforeModelChange();
});
connect(model(), &QAbstractItemModel::rowsInserted, this, [this]() {
d->restoreCurrentSelectionAfterModelChange();
});
connect(model(), &QAbstractItemModel::rowsAboutToBeRemoved, this, [this]() {
d->storeCurrentSelectionBeforeModelChange();
});
connect(model(), &QAbstractItemModel::rowsRemoved, this, [this]() {
d->restoreCurrentSelectionAfterModelChange();
});
connect(model(), &QAbstractItemModel::modelAboutToBeReset, this, [this]() {
d->storeCurrentSelectionBeforeModelChange();
});
connect(model(), &QAbstractItemModel::modelReset, this, [this]() {
d->restoreCurrentSelectionAfterModelChange();
});
QTimer::singleShot(0, this, &KeySelectionCombo::init);
}
KeySelectionCombo::~KeySelectionCombo() = default;
void KeySelectionCombo::init()
{
connect(d->cache.get(), &Kleo::KeyCache::keyListingDone, this, [this]() {
// Set useKeyCache ensures that the cache is populated
// so this can be a blocking call if the cache is not initialized
if (!d->initialKeyListingDone) {
d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
d->proxyModel->removeCustomItem(QStringLiteral("-libkleo-loading-keys"));
}
// We use the useWasEnabled state variable to decide if we should
// change the enable / disable state based on the keylist done signal.
// If we triggered the refresh useWasEnabled is true and we want to
// enable / disable again after our refresh, as the refresh disabled it.
//
// But if a keyListingDone signal comes from just a generic refresh
// triggered by someone else we don't want to change the enable / disable
// state.
if (d->useWasEnabled) {
setEnabled(d->wasEnabled);
d->useWasEnabled = false;
}
Q_EMIT keyListingFinished();
});
connect(this, &KeySelectionCombo::keyListingFinished, this, [this]() {
if (!d->initialKeyListingDone) {
d->updateWithDefaultKey();
d->initialKeyListingDone = true;
}
});
if (!d->cache->initialized()) {
refreshKeys();
} else {
d->model->useKeyCache(true, d->secretOnly ? KeyList::SecretKeysOnly : KeyList::AllKeys);
Q_EMIT keyListingFinished();
}
connect(this, qOverload<int>(&QComboBox::currentIndexChanged), this, [this]() {
setToolTip(currentData(Qt::ToolTipRole).toString());
});
}
void KeySelectionCombo::setKeyFilter(const std::shared_ptr<const KeyFilter> &kf)
{
d->sortFilterProxy->setKeyFilter(kf);
d->proxyModel->sort(0);
d->updateWithDefaultKey();
}
std::shared_ptr<const KeyFilter> KeySelectionCombo::keyFilter() const
{
return d->sortFilterProxy->keyFilter();
}
void KeySelectionCombo::setIdFilter(const QString &id)
{
d->sortFilterProxy->setFilterRegularExpression(id);
d->mPerfectMatchMbox = id;
d->updateWithDefaultKey();
}
QString KeySelectionCombo::idFilter() const
{
return d->sortFilterProxy->filterRegularExpression().pattern();
}
GpgME::Key Kleo::KeySelectionCombo::currentKey() const
{
return currentData(KeyList::KeyRole).value<GpgME::Key>();
}
void Kleo::KeySelectionCombo::setCurrentKey(const GpgME::Key &key)
{
const int idx = findData(QString::fromLatin1(key.primaryFingerprint()), KeyList::FingerprintRole, Qt::MatchExactly);
if (idx > -1) {
setCurrentIndex(idx);
} else if (!d->selectPerfectIdMatch()) {
d->updateWithDefaultKey();
}
setToolTip(currentData(Qt::ToolTipRole).toString());
}
void Kleo::KeySelectionCombo::setCurrentKey(const QString &fingerprint)
{
const auto cur = currentKey();
if (!cur.isNull() && !fingerprint.isEmpty() && fingerprint == QLatin1String(cur.primaryFingerprint())) {
// already set; still emit a changed signal because the current key may
// have become the item at the current index by changes in the underlying model
Q_EMIT currentKeyChanged(cur);
return;
}
const int idx = findData(fingerprint, KeyList::FingerprintRole, Qt::MatchExactly);
if (idx > -1) {
setCurrentIndex(idx);
} else if (!d->selectPerfectIdMatch()) {
setCurrentIndex(0);
}
setToolTip(currentData(Qt::ToolTipRole).toString());
}
void KeySelectionCombo::refreshKeys()
{
d->wasEnabled = isEnabled();
d->useWasEnabled = true;
setEnabled(false);
const bool wasBlocked = blockSignals(true);
prependCustomItem(QIcon(), i18n("Loading keys ..."), QStringLiteral("-libkleo-loading-keys"));
setCurrentIndex(0);
blockSignals(wasBlocked);
d->cache->startKeyListing();
}
void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
d->proxyModel->appendItem(icon, text, data, toolTip);
}
void KeySelectionCombo::appendCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
{
appendCustomItem(icon, text, data, QString());
}
void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data, const QString &toolTip)
{
d->proxyModel->prependItem(icon, text, data, toolTip);
}
void KeySelectionCombo::prependCustomItem(const QIcon &icon, const QString &text, const QVariant &data)
{
prependCustomItem(icon, text, data, QString());
}
void KeySelectionCombo::removeCustomItem(const QVariant &data)
{
d->proxyModel->removeCustomItem(data);
}
void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint, GpgME::Protocol proto)
{
d->defaultKeys.insert(proto, fingerprint);
d->updateWithDefaultKey();
}
void Kleo::KeySelectionCombo::setDefaultKey(const QString &fingerprint)
{
setDefaultKey(fingerprint, GpgME::UnknownProtocol);
}
QString Kleo::KeySelectionCombo::defaultKey(GpgME::Protocol proto) const
{
return d->defaultKeys.value(proto);
}
QString Kleo::KeySelectionCombo::defaultKey() const
{
return defaultKey(GpgME::UnknownProtocol);
}
#include "keyselectioncombo.moc"
diff --git a/src/ui/keyselectiondialog.cpp b/src/ui/keyselectiondialog.cpp
index 9019fd1e..a0fd6213 100644
--- a/src/ui/keyselectiondialog.cpp
+++ b/src/ui/keyselectiondialog.cpp
@@ -1,998 +1,999 @@
/* -*- c++ -*-
keyselectiondialog.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
Based on kpgpui.cpp
SPDX-FileCopyrightText: 2001, 2002 the KPGP authors
See file libkdenetwork/AUTHORS.kpgp for details
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keyselectiondialog.h"
#include "keylistview.h"
#include "progressdialog.h"
-#include "kleo_ui_debug.h"
#include "libkleo/dn.h"
+#include <kleo_ui_debug.h>
+
// gpgme++
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <qgpgme/keylistjob.h>
// KDE
#include <KConfig>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
// Qt
#include <QApplication>
#include <QCheckBox>
#include <QDateTime>
#include <QDialogButtonBox>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QProcess>
#include <QPushButton>
#include <QRegExp>
#include <QScrollBar>
#include <QTimer>
#include <QVBoxLayout>
#include <algorithm>
#include <iterator>
#include <string.h>
static bool checkKeyUsage(const GpgME::Key &key, unsigned int keyUsage, QString *statusString = nullptr)
{
auto setStatusString = [statusString](const QString &status) {
if (statusString) {
*statusString = status;
}
};
if (keyUsage & Kleo::KeySelectionDialog::ValidKeys) {
if (key.isInvalid()) {
if (key.keyListMode() & GpgME::Validate) {
qCDebug(KLEO_UI_LOG) << "key is invalid";
setStatusString(i18n("The key is not valid."));
return false;
} else {
qCDebug(KLEO_UI_LOG) << "key is invalid - ignoring";
}
}
if (key.isExpired()) {
qCDebug(KLEO_UI_LOG) << "key is expired";
setStatusString(i18n("The key is expired."));
return false;
} else if (key.isRevoked()) {
qCDebug(KLEO_UI_LOG) << "key is revoked";
setStatusString(i18n("The key is revoked."));
return false;
} else if (key.isDisabled()) {
qCDebug(KLEO_UI_LOG) << "key is disabled";
setStatusString(i18n("The key is disabled."));
return false;
}
}
if (keyUsage & Kleo::KeySelectionDialog::EncryptionKeys && !key.canEncrypt()) {
qCDebug(KLEO_UI_LOG) << "key can't encrypt";
setStatusString(i18n("The key is not designated for encryption."));
return false;
}
if (keyUsage & Kleo::KeySelectionDialog::SigningKeys && !key.canSign()) {
qCDebug(KLEO_UI_LOG) << "key can't sign";
setStatusString(i18n("The key is not designated for signing."));
return false;
}
if (keyUsage & Kleo::KeySelectionDialog::CertificationKeys && !key.canCertify()) {
qCDebug(KLEO_UI_LOG) << "key can't certify";
setStatusString(i18n("The key is not designated for certifying."));
return false;
}
if (keyUsage & Kleo::KeySelectionDialog::AuthenticationKeys && !key.canAuthenticate()) {
qCDebug(KLEO_UI_LOG) << "key can't authenticate";
setStatusString(i18n("The key is not designated for authentication."));
return false;
}
if (keyUsage & Kleo::KeySelectionDialog::SecretKeys && !(keyUsage & Kleo::KeySelectionDialog::PublicKeys) && !key.hasSecret()) {
qCDebug(KLEO_UI_LOG) << "key isn't secret";
setStatusString(i18n("The key is not secret."));
return false;
}
if (keyUsage & Kleo::KeySelectionDialog::TrustedKeys && key.protocol() == GpgME::OpenPGP &&
// only check this for secret keys for now.
// Seems validity isn't checked for secret keylistings...
!key.hasSecret()) {
std::vector<GpgME::UserID> uids = key.userIDs();
for (std::vector<GpgME::UserID>::const_iterator it = uids.begin(); it != uids.end(); ++it) {
if (!it->isRevoked() && it->validity() >= GpgME::UserID::Marginal) {
return true;
}
}
qCDebug(KLEO_UI_LOG) << "key has no UIDs with validity >= Marginal";
setStatusString(i18n("The key is not trusted enough."));
return false;
}
// X.509 keys are always trusted, else they won't be the keybox.
// PENDING(marc) check that this ^ is correct
setStatusString(i18n("The key can be used."));
return true;
}
static bool checkKeyUsage(const std::vector<GpgME::Key> &keys, unsigned int keyUsage)
{
for (auto it = keys.begin(); it != keys.end(); ++it) {
if (!checkKeyUsage(*it, keyUsage)) {
return false;
}
}
return true;
}
static inline QString time_t2string(time_t t)
{
return QDateTime::fromSecsSinceEpoch(t).toString();
}
namespace
{
class ColumnStrategy : public Kleo::KeyListView::ColumnStrategy
{
public:
ColumnStrategy(unsigned int keyUsage);
QString title(int col) const override;
int width(int col, const QFontMetrics &fm) const override;
QString text(const GpgME::Key &key, int col) const override;
QString toolTip(const GpgME::Key &key, int col) const override;
QIcon icon(const GpgME::Key &key, int col) const override;
private:
const QIcon mKeyGoodPix, mKeyBadPix, mKeyUnknownPix, mKeyValidPix;
const unsigned int mKeyUsage;
};
static QString iconPath(const QString &name)
{
return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("libkleopatra/pics/") + name + QStringLiteral(".png"));
}
ColumnStrategy::ColumnStrategy(unsigned int keyUsage)
: Kleo::KeyListView::ColumnStrategy()
, mKeyGoodPix(iconPath(QStringLiteral("key_ok")))
, mKeyBadPix(iconPath(QStringLiteral("key_bad")))
, mKeyUnknownPix(iconPath(QStringLiteral("key_unknown")))
, mKeyValidPix(iconPath(QStringLiteral("key")))
, mKeyUsage(keyUsage)
{
if (keyUsage == 0) {
qCWarning(KLEO_UI_LOG) << "KeySelectionDialog: keyUsage == 0. You want to use AllKeys instead.";
}
}
QString ColumnStrategy::title(int col) const
{
switch (col) {
case 0:
return i18n("Key ID");
case 1:
return i18n("User ID");
default:
return QString();
}
}
int ColumnStrategy::width(int col, const QFontMetrics &fm) const
{
if (col == 0) {
static const char hexchars[] = "0123456789ABCDEF";
int maxWidth = 0;
for (unsigned int i = 0; i < 16; ++i) {
maxWidth = qMax(fm.boundingRect(QLatin1Char(hexchars[i])).width(), maxWidth);
}
return 8 * maxWidth + 2 * 16 /* KIconLoader::SizeSmall */;
}
return Kleo::KeyListView::ColumnStrategy::width(col, fm);
}
QString ColumnStrategy::text(const GpgME::Key &key, int col) const
{
switch (col) {
case 0: {
if (key.shortKeyID()) {
return QString::fromUtf8(key.shortKeyID());
} else {
return xi18n("<placeholder>unknown</placeholder>");
}
}
case 1: {
const char *uid = key.userID(0).id();
if (key.protocol() == GpgME::OpenPGP) {
return uid && *uid ? QString::fromUtf8(uid) : QString();
} else { // CMS
return Kleo::DN(uid).prettyDN();
}
}
default:
return QString();
}
}
QString ColumnStrategy::toolTip(const GpgME::Key &key, int) const
{
const char *uid = key.userID(0).id();
const char *fpr = key.primaryFingerprint();
const char *issuer = key.issuerName();
const GpgME::Subkey subkey = key.subkey(0);
const QString expiry = subkey.neverExpires() ? i18n("never") : time_t2string(subkey.expirationTime());
const QString creation = time_t2string(subkey.creationTime());
QString keyStatusString;
if (!checkKeyUsage(key, mKeyUsage, &keyStatusString)) {
// Show the status in bold if there is a problem
keyStatusString = QLatin1String("<b>") % keyStatusString % QLatin1String("</b>");
}
QString html = QStringLiteral("<qt><p style=\"style='white-space:pre'\">");
if (key.protocol() == GpgME::OpenPGP) {
html += i18n("OpenPGP key for <b>%1</b>", uid ? QString::fromUtf8(uid) : i18n("unknown"));
} else {
html += i18n("S/MIME key for <b>%1</b>", uid ? Kleo::DN(uid).prettyDN() : i18n("unknown"));
}
html += QStringLiteral("</p><table>");
const auto addRow = [&html](const QString &name, const QString &value) {
html += QStringLiteral("<tr><td align=\"right\"><b>%1: </b></td><td>%2</td></tr>").arg(name, value);
};
addRow(i18nc("Key creation date", "Created"), creation);
addRow(i18nc("Key Expiration date", "Expiry"), expiry);
addRow(i18nc("Key fingerprint", "Fingerprint"), fpr ? QString::fromLatin1(fpr) : i18n("unknown"));
if (key.protocol() != GpgME::OpenPGP) {
addRow(i18nc("Key issuer", "Issuer"), issuer ? Kleo::DN(issuer).prettyDN() : i18n("unknown"));
}
addRow(i18nc("Key status", "Status"), keyStatusString);
html += QStringLiteral("</table></qt>");
return html;
}
QIcon ColumnStrategy::icon(const GpgME::Key &key, int col) const
{
if (col != 0) {
return QIcon();
}
// this key did not undergo a validating keylisting yet:
if (!(key.keyListMode() & GpgME::Validate)) {
return mKeyUnknownPix;
}
if (!checkKeyUsage(key, mKeyUsage)) {
return mKeyBadPix;
}
if (key.protocol() == GpgME::CMS) {
return mKeyGoodPix;
}
switch (key.userID(0).validity()) {
default:
case GpgME::UserID::Unknown:
case GpgME::UserID::Undefined:
return mKeyUnknownPix;
case GpgME::UserID::Never:
return mKeyValidPix;
case GpgME::UserID::Marginal:
case GpgME::UserID::Full:
case GpgME::UserID::Ultimate:
return mKeyGoodPix;
}
}
}
static const int sCheckSelectionDelay = 250;
Kleo::KeySelectionDialog::KeySelectionDialog(QWidget *parent, Options options)
: QDialog(parent)
, mOpenPGPBackend(QGpgME::openpgp())
, mSMIMEBackend(QGpgME::smime())
, mKeyUsage(AllKeys)
{
qCDebug(KLEO_UI_LOG) << "mTruncated:" << mTruncated << "mSavedOffsetY:" << mSavedOffsetY;
setUpUI(options, QString());
}
Kleo::KeySelectionDialog::KeySelectionDialog(const QString &title,
const QString &text,
const std::vector<GpgME::Key> &selectedKeys,
unsigned int keyUsage,
bool extendedSelection,
bool rememberChoice,
QWidget *parent,
bool modal)
: QDialog(parent)
, mSelectedKeys(selectedKeys)
, mKeyUsage(keyUsage)
{
setWindowTitle(title);
setModal(modal);
init(rememberChoice, extendedSelection, text, QString());
}
Kleo::KeySelectionDialog::KeySelectionDialog(const QString &title,
const QString &text,
const QString &initialQuery,
const std::vector<GpgME::Key> &selectedKeys,
unsigned int keyUsage,
bool extendedSelection,
bool rememberChoice,
QWidget *parent,
bool modal)
: QDialog(parent)
, mSelectedKeys(selectedKeys)
, mKeyUsage(keyUsage)
, mSearchText(initialQuery)
, mInitialQuery(initialQuery)
{
setWindowTitle(title);
setModal(modal);
init(rememberChoice, extendedSelection, text, initialQuery);
}
Kleo::KeySelectionDialog::KeySelectionDialog(const QString &title,
const QString &text,
const QString &initialQuery,
unsigned int keyUsage,
bool extendedSelection,
bool rememberChoice,
QWidget *parent,
bool modal)
: QDialog(parent)
, mKeyUsage(keyUsage)
, mSearchText(initialQuery)
, mInitialQuery(initialQuery)
{
setWindowTitle(title);
setModal(modal);
init(rememberChoice, extendedSelection, text, initialQuery);
}
void Kleo::KeySelectionDialog::setUpUI(Options options, const QString &initialQuery)
{
auto mainLayout = new QVBoxLayout(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
mOkButton = buttonBox->button(QDialogButtonBox::Ok);
mOkButton->setDefault(true);
mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return);
mCheckSelectionTimer = new QTimer(this);
mStartSearchTimer = new QTimer(this);
QFrame *page = new QFrame(this);
mainLayout->addWidget(page);
mainLayout->addWidget(buttonBox);
mTopLayout = new QVBoxLayout(page);
mTopLayout->setContentsMargins(0, 0, 0, 0);
mTextLabel = new QLabel(page);
mTextLabel->setWordWrap(true);
// Setting the size policy is necessary as a workaround for https://issues.kolab.org/issue4429
// and http://bugreports.qt.nokia.com/browse/QTBUG-8740
mTextLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
connect(mTextLabel, &QLabel::linkActivated, this, &KeySelectionDialog::slotStartCertificateManager);
mTopLayout->addWidget(mTextLabel);
mTextLabel->hide();
QPushButton *const searchExternalPB = new QPushButton(i18n("Search for &External Certificates"), page);
mTopLayout->addWidget(searchExternalPB, 0, Qt::AlignLeft);
connect(searchExternalPB, &QAbstractButton::clicked, this, &KeySelectionDialog::slotStartSearchForExternalCertificates);
if (initialQuery.isEmpty()) {
searchExternalPB->hide();
}
auto hlay = new QHBoxLayout();
mTopLayout->addLayout(hlay);
auto le = new QLineEdit(page);
le->setClearButtonEnabled(true);
le->setText(initialQuery);
QLabel *lbSearchFor = new QLabel(i18n("&Search for:"), page);
lbSearchFor->setBuddy(le);
hlay->addWidget(lbSearchFor);
hlay->addWidget(le, 1);
le->setFocus();
connect(le, &QLineEdit::textChanged, this, [this](const QString &s) {
slotSearch(s);
});
connect(mStartSearchTimer, &QTimer::timeout, this, &KeySelectionDialog::slotFilter);
mKeyListView = new KeyListView(new ColumnStrategy(mKeyUsage), nullptr, page);
mKeyListView->setObjectName(QStringLiteral("mKeyListView"));
mKeyListView->header()->stretchLastSection();
mKeyListView->setRootIsDecorated(true);
mKeyListView->setSortingEnabled(true);
mKeyListView->header()->setSortIndicatorShown(true);
mKeyListView->header()->setSortIndicator(1, Qt::AscendingOrder); // sort by User ID
if (options & ExtendedSelection) {
mKeyListView->setSelectionMode(QAbstractItemView::ExtendedSelection);
}
mTopLayout->addWidget(mKeyListView, 10);
if (options & RememberChoice) {
mRememberCB = new QCheckBox(i18n("&Remember choice"), page);
mTopLayout->addWidget(mRememberCB);
mRememberCB->setWhatsThis(
i18n("<qt><p>If you check this box your choice will "
"be stored and you will not be asked again."
"</p></qt>"));
}
connect(mCheckSelectionTimer, &QTimer::timeout, this, [this]() {
slotCheckSelection();
});
connectSignals();
connect(mKeyListView, &Kleo::KeyListView::doubleClicked, this, &KeySelectionDialog::slotTryOk);
connect(mKeyListView, &KeyListView::contextMenu, this, &KeySelectionDialog::slotRMB);
if (options & RereadKeys) {
QPushButton *button = new QPushButton(i18n("&Reread Keys"));
buttonBox->addButton(button, QDialogButtonBox::ActionRole);
connect(button, &QPushButton::clicked, this, &KeySelectionDialog::slotRereadKeys);
}
if (options & ExternalCertificateManager) {
QPushButton *button = new QPushButton(i18n("&Start Certificate Manager"));
buttonBox->addButton(button, QDialogButtonBox::ActionRole);
connect(button, &QPushButton::clicked, this, [this]() {
slotStartCertificateManager();
});
}
connect(mOkButton, &QPushButton::clicked, this, &KeySelectionDialog::slotOk);
connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &KeySelectionDialog::slotCancel);
mTopLayout->activate();
if (qApp) {
QSize dialogSize(sizeHint());
KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), "Key Selection Dialog");
dialogSize = dialogConfig.readEntry("Dialog size", dialogSize);
const QByteArray headerState = dialogConfig.readEntry("header", QByteArray());
if (!headerState.isEmpty()) {
mKeyListView->header()->restoreState(headerState);
}
resize(dialogSize);
}
}
void Kleo::KeySelectionDialog::init(bool rememberChoice, bool extendedSelection, const QString &text, const QString &initialQuery)
{
Options options = {RereadKeys, ExternalCertificateManager};
options.setFlag(ExtendedSelection, extendedSelection);
options.setFlag(RememberChoice, rememberChoice);
setUpUI(options, initialQuery);
setText(text);
if (mKeyUsage & OpenPGPKeys) {
mOpenPGPBackend = QGpgME::openpgp();
}
if (mKeyUsage & SMIMEKeys) {
mSMIMEBackend = QGpgME::smime();
}
slotRereadKeys();
}
Kleo::KeySelectionDialog::~KeySelectionDialog()
{
disconnectSignals();
KConfigGroup dialogConfig(KSharedConfig::openStateConfig(), "Key Selection Dialog");
dialogConfig.writeEntry("Dialog size", size());
dialogConfig.writeEntry("header", mKeyListView->header()->saveState());
dialogConfig.sync();
}
void Kleo::KeySelectionDialog::setText(const QString &text)
{
mTextLabel->setText(text);
mTextLabel->setVisible(!text.isEmpty());
}
void Kleo::KeySelectionDialog::setKeys(const std::vector<GpgME::Key> &keys)
{
for (const GpgME::Key &key : keys) {
mKeyListView->slotAddKey(key);
}
}
void Kleo::KeySelectionDialog::connectSignals()
{
if (mKeyListView->isMultiSelection()) {
connect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged);
} else {
connect(mKeyListView,
qOverload<KeyListViewItem *>(&KeyListView::selectionChanged),
this,
qOverload<KeyListViewItem *>(&KeySelectionDialog::slotCheckSelection));
}
}
void Kleo::KeySelectionDialog::disconnectSignals()
{
if (mKeyListView->isMultiSelection()) {
disconnect(mKeyListView, &QTreeWidget::itemSelectionChanged, this, &KeySelectionDialog::slotSelectionChanged);
} else {
disconnect(mKeyListView,
qOverload<KeyListViewItem *>(&KeyListView::selectionChanged),
this,
qOverload<KeyListViewItem *>(&KeySelectionDialog::slotCheckSelection));
}
}
const GpgME::Key &Kleo::KeySelectionDialog::selectedKey() const
{
static const GpgME::Key null = GpgME::Key::null;
if (mKeyListView->isMultiSelection() || !mKeyListView->selectedItem()) {
return null;
}
return mKeyListView->selectedItem()->key();
}
QString Kleo::KeySelectionDialog::fingerprint() const
{
return QLatin1String(selectedKey().primaryFingerprint());
}
QStringList Kleo::KeySelectionDialog::fingerprints() const
{
QStringList result;
for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
if (const char *fpr = it->primaryFingerprint()) {
result.push_back(QLatin1String(fpr));
}
}
return result;
}
QStringList Kleo::KeySelectionDialog::pgpKeyFingerprints() const
{
QStringList result;
for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
if (it->protocol() == GpgME::OpenPGP) {
if (const char *fpr = it->primaryFingerprint()) {
result.push_back(QLatin1String(fpr));
}
}
}
return result;
}
QStringList Kleo::KeySelectionDialog::smimeFingerprints() const
{
QStringList result;
for (auto it = mSelectedKeys.begin(); it != mSelectedKeys.end(); ++it) {
if (it->protocol() == GpgME::CMS) {
if (const char *fpr = it->primaryFingerprint()) {
result.push_back(QLatin1String(fpr));
}
}
}
return result;
}
void Kleo::KeySelectionDialog::slotRereadKeys()
{
mKeyListView->clear();
mListJobCount = 0;
mTruncated = 0;
mSavedOffsetY = mKeyListView->verticalScrollBar()->value();
disconnectSignals();
mKeyListView->setEnabled(false);
// FIXME: save current selection
if (mOpenPGPBackend) {
startKeyListJobForBackend(mOpenPGPBackend, std::vector<GpgME::Key>(), false /*non-validating*/);
}
if (mSMIMEBackend) {
startKeyListJobForBackend(mSMIMEBackend, std::vector<GpgME::Key>(), false /*non-validating*/);
}
if (mListJobCount == 0) {
mKeyListView->setEnabled(true);
KMessageBox::information(this,
i18n("No backends found for listing keys. "
"Check your installation."),
i18n("Key Listing Failed"));
connectSignals();
}
}
void Kleo::KeySelectionDialog::slotStartCertificateManager(const QString &query)
{
QStringList args;
if (!query.isEmpty()) {
args << QStringLiteral("--search") << query;
}
const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra"));
if (exec.isEmpty()) {
qCWarning(KLEO_UI_LOG) << "Could not find kleopatra executable in PATH";
KMessageBox::error(this,
i18n("Could not start certificate manager; "
"please check your installation."),
i18n("Certificate Manager Error"));
} else {
QProcess::startDetached(QStringLiteral("kleopatra"), args);
qCDebug(KLEO_UI_LOG) << "\nslotStartCertManager(): certificate manager started.";
}
}
#ifndef __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
#define __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
static void showKeyListError(QWidget *parent, const GpgME::Error &err)
{
Q_ASSERT(err);
const QString msg = i18n(
"<qt><p>An error occurred while fetching "
"the keys from the backend:</p>"
"<p><b>%1</b></p></qt>",
QString::fromLocal8Bit(err.asString()));
KMessageBox::error(parent, msg, i18n("Key Listing Failed"));
}
#endif // __KLEO_UI_SHOW_KEY_LIST_ERROR_H__
namespace
{
struct ExtractFingerprint {
QString operator()(const GpgME::Key &key)
{
return QLatin1String(key.primaryFingerprint());
}
};
}
void Kleo::KeySelectionDialog::startKeyListJobForBackend(const QGpgME::Protocol *backend, const std::vector<GpgME::Key> &keys, bool validate)
{
Q_ASSERT(backend);
QGpgME::KeyListJob *job = backend->keyListJob(false, false, validate); // local, w/o sigs, validation as given
if (!job) {
return;
}
connect(job, &QGpgME::KeyListJob::result, this, &KeySelectionDialog::slotKeyListResult);
if (validate) {
connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotRefreshKey);
} else {
connect(job, &QGpgME::KeyListJob::nextKey, mKeyListView, &KeyListView::slotAddKey);
}
QStringList fprs;
std::transform(keys.begin(), keys.end(), std::back_inserter(fprs), ExtractFingerprint());
const GpgME::Error err = job->start(fprs, mKeyUsage & SecretKeys && !(mKeyUsage & PublicKeys));
if (err) {
return showKeyListError(this, err);
}
#ifndef LIBKLEO_NO_PROGRESSDIALOG
// FIXME: create a MultiProgressDialog:
(void)new ProgressDialog(job, validate ? i18n("Checking selected keys...") : i18n("Fetching keys..."), this);
#endif
++mListJobCount;
}
static void selectKeys(Kleo::KeyListView *klv, const std::vector<GpgME::Key> &selectedKeys)
{
klv->clearSelection();
if (selectedKeys.empty()) {
return;
}
for (auto it = selectedKeys.begin(); it != selectedKeys.end(); ++it) {
if (Kleo::KeyListViewItem *item = klv->itemByFingerprint(it->primaryFingerprint())) {
item->setSelected(true);
}
}
}
void Kleo::KeySelectionDialog::slotKeyListResult(const GpgME::KeyListResult &res)
{
if (res.error()) {
showKeyListError(this, res.error());
} else if (res.isTruncated()) {
++mTruncated;
}
if (--mListJobCount > 0) {
return; // not yet finished...
}
if (mTruncated > 0) {
KMessageBox::information(this,
i18np("<qt>One backend returned truncated output.<p>"
"Not all available keys are shown</p></qt>",
"<qt>%1 backends returned truncated output.<p>"
"Not all available keys are shown</p></qt>",
mTruncated),
i18n("Key List Result"));
}
mKeyListView->flushKeys();
mKeyListView->setEnabled(true);
mListJobCount = mTruncated = 0;
mKeysToCheck.clear();
selectKeys(mKeyListView, mSelectedKeys);
slotFilter();
connectSignals();
slotSelectionChanged();
// restore the saved position of the contents
mKeyListView->verticalScrollBar()->setValue(mSavedOffsetY);
mSavedOffsetY = 0;
}
void Kleo::KeySelectionDialog::slotSelectionChanged()
{
qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotSelectionChanged()";
// (re)start the check selection timer. Checking the selection is delayed
// because else drag-selection doesn't work very good (checking key trust
// is slow).
mCheckSelectionTimer->start(sCheckSelectionDelay);
}
namespace
{
struct AlreadyChecked {
bool operator()(const GpgME::Key &key) const
{
return key.keyListMode() & GpgME::Validate;
}
};
}
void Kleo::KeySelectionDialog::slotCheckSelection(KeyListViewItem *item)
{
qCDebug(KLEO_UI_LOG) << "KeySelectionDialog::slotCheckSelection()";
mCheckSelectionTimer->stop();
mSelectedKeys.clear();
if (!mKeyListView->isMultiSelection()) {
if (item) {
mSelectedKeys.push_back(item->key());
}
}
for (KeyListViewItem *it = mKeyListView->firstChild(); it; it = it->nextSibling()) {
if (it->isSelected()) {
mSelectedKeys.push_back(it->key());
}
}
mKeysToCheck.clear();
std::remove_copy_if(mSelectedKeys.begin(), mSelectedKeys.end(), std::back_inserter(mKeysToCheck), AlreadyChecked());
if (mKeysToCheck.empty()) {
mOkButton->setEnabled(!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage));
return;
}
// performed all fast checks - now for validating key listing:
startValidatingKeyListing();
}
void Kleo::KeySelectionDialog::startValidatingKeyListing()
{
if (mKeysToCheck.empty()) {
return;
}
mListJobCount = 0;
mTruncated = 0;
mSavedOffsetY = mKeyListView->verticalScrollBar()->value();
disconnectSignals();
mKeyListView->setEnabled(false);
std::vector<GpgME::Key> smime;
std::vector<GpgME::Key> openpgp;
for (std::vector<GpgME::Key>::const_iterator it = mKeysToCheck.begin(); it != mKeysToCheck.end(); ++it) {
if (it->protocol() == GpgME::OpenPGP) {
openpgp.push_back(*it);
} else {
smime.push_back(*it);
}
}
if (!openpgp.empty()) {
Q_ASSERT(mOpenPGPBackend);
startKeyListJobForBackend(mOpenPGPBackend, openpgp, true /*validate*/);
}
if (!smime.empty()) {
Q_ASSERT(mSMIMEBackend);
startKeyListJobForBackend(mSMIMEBackend, smime, true /*validate*/);
}
Q_ASSERT(mListJobCount > 0);
}
bool Kleo::KeySelectionDialog::rememberSelection() const
{
return mRememberCB && mRememberCB->isChecked();
}
void Kleo::KeySelectionDialog::slotRMB(Kleo::KeyListViewItem *item, const QPoint &p)
{
if (!item) {
return;
}
mCurrentContextMenuItem = item;
QMenu menu;
menu.addAction(i18n("Recheck Key"), this, &KeySelectionDialog::slotRecheckKey);
menu.exec(p);
}
void Kleo::KeySelectionDialog::slotRecheckKey()
{
if (!mCurrentContextMenuItem || mCurrentContextMenuItem->key().isNull()) {
return;
}
mKeysToCheck.clear();
mKeysToCheck.push_back(mCurrentContextMenuItem->key());
}
void Kleo::KeySelectionDialog::slotTryOk()
{
if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
slotOk();
}
}
void Kleo::KeySelectionDialog::slotOk()
{
if (mCheckSelectionTimer->isActive()) {
slotCheckSelection();
}
#if 0 // Laurent I don't understand why we returns here.
// button could be disabled again after checking the selected key1
if (!mSelectedKeys.empty() && checkKeyUsage(mSelectedKeys, mKeyUsage)) {
return;
}
#endif
mStartSearchTimer->stop();
accept();
}
void Kleo::KeySelectionDialog::slotCancel()
{
mCheckSelectionTimer->stop();
mStartSearchTimer->stop();
reject();
}
void Kleo::KeySelectionDialog::slotSearch(const QString &text)
{
mSearchText = text.trimmed().toUpper();
slotSearch();
}
void Kleo::KeySelectionDialog::slotSearch()
{
mStartSearchTimer->setSingleShot(true);
mStartSearchTimer->start(sCheckSelectionDelay);
}
void Kleo::KeySelectionDialog::slotFilter()
{
if (mSearchText.isEmpty()) {
showAllItems();
return;
}
// OK, so we need to filter:
QRegExp keyIdRegExp(QLatin1String("(?:0x)?[A-F0-9]{1,8}"), Qt::CaseInsensitive);
if (keyIdRegExp.exactMatch(mSearchText)) {
if (mSearchText.startsWith(QLatin1String("0X")))
// search for keyID only:
{
filterByKeyID(mSearchText.mid(2));
} else
// search for UID and keyID:
{
filterByKeyIDOrUID(mSearchText);
}
} else {
// search in UID:
filterByUID(mSearchText);
}
}
void Kleo::KeySelectionDialog::filterByKeyID(const QString &keyID)
{
Q_ASSERT(keyID.length() <= 8);
Q_ASSERT(!keyID.isEmpty()); // regexp in slotFilter should prevent these
if (keyID.isEmpty()) {
showAllItems();
} else {
for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
item->setHidden(!item->text(0).toUpper().startsWith(keyID));
}
}
}
static bool anyUIDMatches(const Kleo::KeyListViewItem *item, QRegExp &rx)
{
if (!item) {
return false;
}
const std::vector<GpgME::UserID> uids = item->key().userIDs();
for (auto it = uids.begin(); it != uids.end(); ++it) {
if (it->id() && rx.indexIn(QString::fromUtf8(it->id())) >= 0) {
return true;
}
}
return false;
}
void Kleo::KeySelectionDialog::filterByKeyIDOrUID(const QString &str)
{
Q_ASSERT(!str.isEmpty());
// match beginnings of words:
QRegExp rx(QLatin1String("\\b") + QRegExp::escape(str), Qt::CaseInsensitive);
for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
item->setHidden(!item->text(0).toUpper().startsWith(str) && !anyUIDMatches(item, rx));
}
}
void Kleo::KeySelectionDialog::filterByUID(const QString &str)
{
Q_ASSERT(!str.isEmpty());
// match beginnings of words:
QRegExp rx(QLatin1String("\\b") + QRegExp::escape(str), Qt::CaseInsensitive);
for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
item->setHidden(!anyUIDMatches(item, rx));
}
}
void Kleo::KeySelectionDialog::showAllItems()
{
for (KeyListViewItem *item = mKeyListView->firstChild(); item; item = item->nextSibling()) {
item->setHidden(false);
}
}
diff --git a/src/ui/messagebox.cpp b/src/ui/messagebox.cpp
index ca11992c..a791fb95 100644
--- a/src/ui/messagebox.cpp
+++ b/src/ui/messagebox.cpp
@@ -1,247 +1,247 @@
/*
messagebox.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "messagebox.h"
#include "auditlogviewer.h"
-#include "kleo_ui_debug.h"
+#include <kleo_ui_debug.h>
#include <gpgme++/encryptionresult.h>
#include <gpgme++/signingresult.h>
#include <qgpgme/job.h>
#include <KGuiItem>
#include <KLocalizedString>
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
#include <KSharedConfig>
#include <gpg-error.h>
using namespace Kleo;
using namespace Kleo::Private;
using namespace GpgME;
using namespace QGpgME;
// static
void MessageBox::auditLog(QWidget *parent, const Job *job, const QString &caption)
{
if (!job) {
return;
}
if (!GpgME::hasFeature(AuditLogFeature, 0) || !job->isAuditLogSupported()) {
KMessageBox::information(parent, i18n("Your system does not have support for GnuPG Audit Logs"), i18n("System Error"));
return;
}
const GpgME::Error err = job->auditLogError();
if (err && err.code() != GPG_ERR_NO_DATA) {
KMessageBox::information(parent,
i18n("An error occurred while trying to retrieve the GnuPG Audit Log:\n%1", QString::fromLocal8Bit(err.asString())),
i18n("GnuPG Audit Log Error"));
return;
}
const QString log = job->auditLogAsHtml();
if (log.isEmpty()) {
KMessageBox::information(parent, i18n("No GnuPG Audit Log available for this operation."), i18n("No GnuPG Audit Log"));
return;
}
auditLog(parent, log, caption);
}
// static
void MessageBox::auditLog(QWidget *parent, const QString &log, const QString &caption)
{
auto const alv = new AuditLogViewer(log, parent);
alv->setAttribute(Qt::WA_DeleteOnClose);
alv->setObjectName(QStringLiteral("alv"));
alv->setWindowTitle(caption);
alv->show();
}
// static
void MessageBox::auditLog(QWidget *parent, const Job *job)
{
auditLog(parent, job, i18n("GnuPG Audit Log Viewer"));
}
// static
void MessageBox::auditLog(QWidget *parent, const QString &log)
{
auditLog(parent, log, i18n("GnuPG Audit Log Viewer"));
}
static QString to_information_string(const SigningResult &result)
{
return (result.error() //
? i18n("Signing failed: %1", QString::fromLocal8Bit(result.error().asString()))
: i18n("Signing successful"));
}
static QString to_error_string(const SigningResult &result)
{
return to_information_string(result);
}
static QString to_information_string(const EncryptionResult &result)
{
return (result.error() //
? i18n("Encryption failed: %1", QString::fromLocal8Bit(result.error().asString()))
: i18n("Encryption successful"));
}
static QString to_error_string(const EncryptionResult &result)
{
return to_information_string(result);
}
static QString to_information_string(const SigningResult &sresult, const EncryptionResult &eresult)
{
return to_information_string(sresult) + QLatin1Char('\n') + to_information_string(eresult);
}
static QString to_error_string(const SigningResult &sresult, const EncryptionResult &eresult)
{
return to_information_string(sresult, eresult);
}
// static
void MessageBox::information(QWidget *parent, const SigningResult &result, const Job *job, KMessageBox::Options options)
{
information(parent, result, job, i18n("Signing Result"), options);
}
// static
void MessageBox::information(QWidget *parent, const SigningResult &result, const Job *job, const QString &caption, KMessageBox::Options options)
{
make(parent, QMessageBox::Information, to_information_string(result), job, caption, options);
}
// static
void MessageBox::error(QWidget *parent, const SigningResult &result, const Job *job, KMessageBox::Options options)
{
error(parent, result, job, i18n("Signing Error"), options);
}
// static
void MessageBox::error(QWidget *parent, const SigningResult &result, const Job *job, const QString &caption, KMessageBox::Options options)
{
make(parent, QMessageBox::Critical, to_error_string(result), job, caption, options);
}
// static
void MessageBox::information(QWidget *parent, const EncryptionResult &result, const Job *job, KMessageBox::Options options)
{
information(parent, result, job, i18n("Encryption Result"), options);
}
// static
void MessageBox::information(QWidget *parent, const EncryptionResult &result, const Job *job, const QString &caption, KMessageBox::Options options)
{
make(parent, QMessageBox::Information, to_information_string(result), job, caption, options);
}
// static
void MessageBox::error(QWidget *parent, const EncryptionResult &result, const Job *job, KMessageBox::Options options)
{
error(parent, result, job, i18n("Encryption Error"), options);
}
// static
void MessageBox::error(QWidget *parent, const EncryptionResult &result, const Job *job, const QString &caption, KMessageBox::Options options)
{
make(parent, QMessageBox::Critical, to_error_string(result), job, caption, options);
}
// static
void MessageBox::information(QWidget *parent, const SigningResult &sresult, const EncryptionResult &eresult, const Job *job, KMessageBox::Options options)
{
information(parent, sresult, eresult, job, i18n("Encryption Result"), options);
}
// static
void MessageBox::information(QWidget *parent,
const SigningResult &sresult,
const EncryptionResult &eresult,
const Job *job,
const QString &caption,
KMessageBox::Options options)
{
make(parent, QMessageBox::Information, to_information_string(sresult, eresult), job, caption, options);
}
// static
void MessageBox::error(QWidget *parent, const SigningResult &sresult, const EncryptionResult &eresult, const Job *job, KMessageBox::Options options)
{
error(parent, sresult, eresult, job, i18n("Encryption Error"), options);
}
// static
void MessageBox::error(QWidget *parent,
const SigningResult &sresult,
const EncryptionResult &eresult,
const Job *job,
const QString &caption,
KMessageBox::Options options)
{
make(parent, QMessageBox::Critical, to_error_string(sresult, eresult), job, caption, options);
}
// static
bool MessageBox::showAuditLogButton(const QGpgME::Job *job)
{
if (!job) {
qCDebug(KLEO_UI_LOG) << "not showing audit log button (no job instance)";
return false;
}
if (!GpgME::hasFeature(GpgME::AuditLogFeature, 0)) {
qCDebug(KLEO_UI_LOG) << "not showing audit log button (gpgme too old)";
return false;
}
if (!job->isAuditLogSupported()) {
qCDebug(KLEO_UI_LOG) << "not showing audit log button (not supported)";
return false;
}
if (job->auditLogError().code() == GPG_ERR_NO_DATA) {
qCDebug(KLEO_UI_LOG) << "not showing audit log button (GPG_ERR_NO_DATA)";
return false;
}
if (!job->auditLogError() && job->auditLogAsHtml().isEmpty()) {
qCDebug(KLEO_UI_LOG) << "not showing audit log button (success, but result empty)";
return false;
}
return true;
}
// static
void MessageBox::make(QWidget *parent, QMessageBox::Icon icon, const QString &text, const Job *job, const QString &caption, KMessageBox::Options options)
{
QDialog *dialog = new QDialog(parent);
dialog->setWindowTitle(caption);
QDialogButtonBox *box = new QDialogButtonBox(showAuditLogButton(job) ? (QDialogButtonBox::Yes | QDialogButtonBox::No) : QDialogButtonBox::Yes, parent);
QPushButton *yesButton = box->button(QDialogButtonBox::Yes);
yesButton->setDefault(true);
// dialog->setEscapeButton(KDialog::Yes);
dialog->setObjectName(QStringLiteral("error"));
dialog->setModal(true);
KGuiItem::assign(yesButton, KStandardGuiItem::ok());
if (GpgME::hasFeature(GpgME::AuditLogFeature, 0)) {
KGuiItem::assign(box->button(QDialogButtonBox::No), KGuiItem(i18n("&Show Audit Log")));
}
if (QDialogButtonBox::No == KMessageBox::createKMessageBox(dialog, box, icon, text, QStringList(), QString(), nullptr, options)) {
auditLog(nullptr, job);
}
}
diff --git a/src/ui/newkeyapprovaldialog.cpp b/src/ui/newkeyapprovaldialog.cpp
index c344dcde..197cba9a 100644
--- a/src/ui/newkeyapprovaldialog.cpp
+++ b/src/ui/newkeyapprovaldialog.cpp
@@ -1,929 +1,929 @@
/* -*- c++ -*-
newkeyapprovaldialog.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2018 Intevation GmbH
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "newkeyapprovaldialog.h"
#include "keyselectioncombo.h"
#include "progressdialog.h"
#include "kleo/debug.h"
#include "kleo/defaultkeyfilter.h"
#include "utils/formatting.h"
#include "utils/gnupg.h"
+#include <libkleo_debug.h>
+
#include <KLocalizedString>
#include <KMessageBox>
#include <QButtonGroup>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMap>
#include <QPushButton>
#include <QRadioButton>
#include <QScreen>
#include <QScrollArea>
#include <QToolTip>
#include <QVBoxLayout>
#include <QWindow>
#include <QGpgME/DefaultKeyGenerationJob>
#include <QGpgME/Job>
#include <gpgme++/key.h>
#include <gpgme++/keygenerationresult.h>
-#include "libkleo_debug.h"
-
using namespace Kleo;
using namespace GpgME;
namespace
{
class EncryptFilter : public DefaultKeyFilter
{
public:
EncryptFilter()
: DefaultKeyFilter()
{
setCanEncrypt(DefaultKeyFilter::Set);
}
};
static std::shared_ptr<KeyFilter> s_encryptFilter = std::shared_ptr<KeyFilter>(new EncryptFilter);
class OpenPGPFilter : public DefaultKeyFilter
{
public:
OpenPGPFilter()
: DefaultKeyFilter()
{
setIsOpenPGP(DefaultKeyFilter::Set);
setCanEncrypt(DefaultKeyFilter::Set);
}
};
static std::shared_ptr<KeyFilter> s_pgpEncryptFilter = std::shared_ptr<KeyFilter>(new OpenPGPFilter);
class OpenPGPSignFilter : public DefaultKeyFilter
{
public:
OpenPGPSignFilter()
: DefaultKeyFilter()
{
/* Also list unusable keys to make it transparent why they are unusable */
setDisabled(DefaultKeyFilter::NotSet);
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setCanSign(DefaultKeyFilter::Set);
setHasSecret(DefaultKeyFilter::Set);
setIsOpenPGP(DefaultKeyFilter::Set);
}
};
static std::shared_ptr<KeyFilter> s_pgpSignFilter = std::shared_ptr<KeyFilter>(new OpenPGPSignFilter);
class SMIMEFilter : public DefaultKeyFilter
{
public:
SMIMEFilter()
: DefaultKeyFilter()
{
setIsOpenPGP(DefaultKeyFilter::NotSet);
setCanEncrypt(DefaultKeyFilter::Set);
}
};
static std::shared_ptr<KeyFilter> s_smimeEncryptFilter = std::shared_ptr<KeyFilter>(new SMIMEFilter);
class SMIMESignFilter : public DefaultKeyFilter
{
public:
SMIMESignFilter()
: DefaultKeyFilter()
{
setDisabled(DefaultKeyFilter::NotSet);
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setCanSign(DefaultKeyFilter::Set);
setIsOpenPGP(DefaultKeyFilter::NotSet);
setHasSecret(DefaultKeyFilter::Set);
}
};
static std::shared_ptr<KeyFilter> s_smimeSignFilter = std::shared_ptr<KeyFilter>(new SMIMESignFilter);
/* Some decoration and a button to remove the filter for a keyselectioncombo */
class ComboWidget : public QWidget
{
Q_OBJECT
public:
explicit ComboWidget(KeySelectionCombo *combo)
: mCombo(combo)
, mFilterBtn(new QPushButton)
{
auto hLay = new QHBoxLayout(this);
auto infoBtn = new QPushButton;
infoBtn->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
infoBtn->setIconSize(QSize(22, 22));
infoBtn->setFlat(true);
hLay->addWidget(infoBtn);
hLay->addWidget(combo, 1);
hLay->addWidget(mFilterBtn, 0);
connect(infoBtn, &QPushButton::clicked, this, [this, infoBtn]() {
QToolTip::showText(infoBtn->mapToGlobal(QPoint()) + QPoint(infoBtn->width(), 0),
mCombo->currentData(Qt::ToolTipRole).toString(),
infoBtn,
QRect(),
30000);
});
// FIXME: This is ugly to enforce but otherwise the
// icon is broken.
combo->setMinimumHeight(22);
mFilterBtn->setMinimumHeight(23);
updateFilterButton();
connect(mFilterBtn, &QPushButton::clicked, this, [this]() {
const QString curFilter = mCombo->idFilter();
if (curFilter.isEmpty()) {
setIdFilter(mLastIdFilter);
mLastIdFilter = QString();
} else {
setIdFilter(QString());
mLastIdFilter = curFilter;
}
});
}
void setIdFilter(const QString &id)
{
mCombo->setIdFilter(id);
updateFilterButton();
}
void updateFilterButton()
{
if (mCombo->idFilter().isEmpty()) {
mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-add-filters")));
mFilterBtn->setToolTip(i18n("Show keys matching the email address"));
} else {
mFilterBtn->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove-filters")));
mFilterBtn->setToolTip(i18n("Show all keys"));
}
}
KeySelectionCombo *combo()
{
return mCombo;
}
GpgME::Protocol fixedProtocol() const
{
return mFixedProtocol;
}
void setFixedProtocol(GpgME::Protocol proto)
{
mFixedProtocol = proto;
}
private:
KeySelectionCombo *mCombo;
QPushButton *mFilterBtn;
QString mLastIdFilter;
GpgME::Protocol mFixedProtocol = GpgME::UnknownProtocol;
};
static enum GpgME::UserID::Validity keyValidity(const GpgME::Key &key)
{
enum GpgME::UserID::Validity validity = GpgME::UserID::Validity::Unknown;
for (const auto &uid : key.userIDs()) {
if (validity == GpgME::UserID::Validity::Unknown || validity > uid.validity()) {
validity = uid.validity();
}
}
return validity;
}
static bool key_has_addr(const GpgME::Key &key, const QString &addr)
{
for (const auto &uid : key.userIDs()) {
if (QString::fromStdString(uid.addrSpec()).toLower() == addr.toLower()) {
return true;
}
}
return false;
}
bool anyKeyHasProtocol(const std::vector<Key> &keys, GpgME::Protocol protocol)
{
return std::any_of(std::begin(keys), std::end(keys), [protocol](const auto &key) {
return key.protocol() == protocol;
});
}
Key findfirstKeyOfType(const std::vector<Key> &keys, GpgME::Protocol protocol)
{
const auto it = std::find_if(std::begin(keys), std::end(keys), [protocol](const auto &key) {
return key.protocol() == protocol;
});
return it != std::end(keys) ? *it : Key();
}
} // namespace
class NewKeyApprovalDialog::Private
{
private:
enum Action {
Unset,
GenerateKey,
IgnoreKey,
};
public:
enum {
OpenPGPButtonId = 1,
SMIMEButtonId = 2,
};
Private(NewKeyApprovalDialog *qq,
bool encrypt,
bool sign,
GpgME::Protocol forcedProtocol,
GpgME::Protocol presetProtocol,
const QString &sender,
bool allowMixed)
: mForcedProtocol{forcedProtocol}
, mSender{sender}
, mSign{sign}
, mEncrypt{encrypt}
, mAllowMixed{allowMixed}
, q{qq}
{
Q_ASSERT(forcedProtocol == GpgME::UnknownProtocol || presetProtocol == GpgME::UnknownProtocol || presetProtocol == forcedProtocol);
Q_ASSERT(!allowMixed || (allowMixed && forcedProtocol == GpgME::UnknownProtocol));
Q_ASSERT(!(!allowMixed && presetProtocol == GpgME::UnknownProtocol));
// We do the translation here to avoid having the same string multiple times.
mGenerateTooltip = i18nc(
"@info:tooltip for a 'Generate new key pair' action "
"in a combobox when a user does not yet have an OpenPGP or S/MIME key.",
"Generate a new key using your E-Mail address.<br/><br/>"
"The key is necessary to decrypt and sign E-Mails. "
"You will be asked for a passphrase to protect this key and the protected key "
"will be stored in your home directory.");
mMainLay = new QVBoxLayout;
QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
mOkButton = btnBox->button(QDialogButtonBox::Ok);
#ifndef NDEBUG
mOkButton->setObjectName(QStringLiteral("ok button"));
#endif
QObject::connect(btnBox, &QDialogButtonBox::accepted, q, [this]() {
accepted();
});
QObject::connect(btnBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
mScrollArea = new QScrollArea;
mScrollArea->setWidget(new QWidget);
mScrollLayout = new QVBoxLayout;
mScrollArea->widget()->setLayout(mScrollLayout);
mScrollArea->setWidgetResizable(true);
mScrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
mScrollArea->setFrameStyle(QFrame::NoFrame);
mScrollLayout->setContentsMargins(0, 0, 0, 0);
q->setWindowTitle(i18nc("@title:window", "Security approval"));
auto fmtLayout = new QHBoxLayout;
mFormatBtns = new QButtonGroup(qq);
QAbstractButton *pgpBtn;
QAbstractButton *smimeBtn;
if (mAllowMixed) {
pgpBtn = new QCheckBox(i18n("OpenPGP"));
smimeBtn = new QCheckBox(i18n("S/MIME"));
} else {
pgpBtn = new QRadioButton(i18n("OpenPGP"));
smimeBtn = new QRadioButton(i18n("S/MIME"));
}
#ifndef NDEBUG
pgpBtn->setObjectName(QStringLiteral("openpgp button"));
smimeBtn->setObjectName(QStringLiteral("smime button"));
#endif
mFormatBtns->addButton(pgpBtn, OpenPGPButtonId);
mFormatBtns->addButton(smimeBtn, SMIMEButtonId);
mFormatBtns->setExclusive(!mAllowMixed);
fmtLayout->addStretch(-1);
fmtLayout->addWidget(pgpBtn);
fmtLayout->addWidget(smimeBtn);
mMainLay->addLayout(fmtLayout);
if (mForcedProtocol != GpgME::UnknownProtocol) {
pgpBtn->setChecked(mForcedProtocol == GpgME::OpenPGP);
smimeBtn->setChecked(mForcedProtocol == GpgME::CMS);
pgpBtn->setVisible(false);
smimeBtn->setVisible(false);
} else {
pgpBtn->setChecked(presetProtocol == GpgME::OpenPGP || presetProtocol == GpgME::UnknownProtocol);
smimeBtn->setChecked(presetProtocol == GpgME::CMS || presetProtocol == GpgME::UnknownProtocol);
}
QObject::connect(mFormatBtns, &QButtonGroup::idClicked, q, [this](int buttonId) {
// ensure that at least one protocol button is checked
if (mAllowMixed && !mFormatBtns->button(OpenPGPButtonId)->isChecked() && !mFormatBtns->button(SMIMEButtonId)->isChecked()) {
mFormatBtns->button(buttonId == OpenPGPButtonId ? SMIMEButtonId : OpenPGPButtonId)->setChecked(true);
}
updateWidgets();
});
mMainLay->addWidget(mScrollArea);
mComplianceLbl = new QLabel;
mComplianceLbl->setVisible(false);
#ifndef NDEBUG
mComplianceLbl->setObjectName(QStringLiteral("compliance label"));
#endif
auto btnLayout = new QHBoxLayout;
btnLayout->addWidget(mComplianceLbl);
btnLayout->addWidget(btnBox);
mMainLay->addLayout(btnLayout);
q->setLayout(mMainLay);
}
~Private() = default;
GpgME::Protocol currentProtocol()
{
const bool openPGPButtonChecked = mFormatBtns->button(OpenPGPButtonId)->isChecked();
const bool smimeButtonChecked = mFormatBtns->button(SMIMEButtonId)->isChecked();
if (mAllowMixed) {
if (openPGPButtonChecked && !smimeButtonChecked) {
return GpgME::OpenPGP;
}
if (!openPGPButtonChecked && smimeButtonChecked) {
return GpgME::CMS;
}
} else if (openPGPButtonChecked) {
return GpgME::OpenPGP;
} else if (smimeButtonChecked) {
return GpgME::CMS;
}
return GpgME::UnknownProtocol;
}
auto findVisibleKeySelectionComboWithGenerateKey()
{
const auto it = std::find_if(std::begin(mAllCombos), std::end(mAllCombos), [](auto combo) {
return combo->isVisible() && combo->currentData(Qt::UserRole).toInt() == GenerateKey;
});
return it != std::end(mAllCombos) ? *it : nullptr;
}
void generateKey(KeySelectionCombo *combo)
{
const auto &addr = combo->property("address").toString();
auto job = new QGpgME::DefaultKeyGenerationJob(q);
auto progress =
new Kleo::ProgressDialog(job, i18n("Generating key for '%1'...", addr) + QStringLiteral("\n\n") + i18n("This can take several minutes."), q);
progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
progress->setWindowTitle(i18nc("@title:window", "Key generation"));
progress->setModal(true);
progress->setAutoClose(true);
progress->setMinimumDuration(0);
progress->setValue(0);
mRunningJobs << job;
connect(job, &QGpgME::DefaultKeyGenerationJob::result, q, [this, job, combo](const GpgME::KeyGenerationResult &result) {
handleKeyGenResult(result, job, combo);
});
job->start(addr, QString());
return;
}
void handleKeyGenResult(const GpgME::KeyGenerationResult &result, QGpgME::Job *job, KeySelectionCombo *combo)
{
mLastError = result.error();
if (!mLastError || mLastError.isCanceled()) {
combo->setDefaultKey(QString::fromLatin1(result.fingerprint()), GpgME::OpenPGP);
connect(combo, &KeySelectionCombo::keyListingFinished, q, [this, job]() {
mRunningJobs.removeAll(job);
});
combo->refreshKeys();
} else {
mRunningJobs.removeAll(job);
}
}
void checkAccepted()
{
if (mLastError || mLastError.isCanceled()) {
KMessageBox::error(q, QString::fromLocal8Bit(mLastError.asString()), i18n("Operation Failed"));
mRunningJobs.clear();
return;
}
if (!mRunningJobs.empty()) {
return;
}
/* Save the keys */
mAcceptedResult.protocol = currentProtocol();
for (const auto combo : std::as_const(mEncCombos)) {
const auto addr = combo->property("address").toString();
const auto key = combo->currentKey();
if (!combo->isVisible() || key.isNull()) {
continue;
}
mAcceptedResult.encryptionKeys[addr].push_back(key);
}
for (const auto combo : std::as_const(mSigningCombos)) {
const auto key = combo->currentKey();
if (!combo->isVisible() || key.isNull()) {
continue;
}
mAcceptedResult.signingKeys.push_back(key);
}
q->accept();
}
void accepted()
{
// We can assume everything was validly resolved, otherwise
// the OK button would have been disabled.
// Handle custom items now.
if (auto combo = findVisibleKeySelectionComboWithGenerateKey()) {
generateKey(combo);
return;
}
checkAccepted();
}
auto encryptionKeyFilter(GpgME::Protocol protocol)
{
switch (protocol) {
case OpenPGP:
return s_pgpEncryptFilter;
case CMS:
return s_smimeEncryptFilter;
default:
return s_encryptFilter;
}
}
void updateWidgets()
{
const GpgME::Protocol protocol = currentProtocol();
const auto encryptionFilter = encryptionKeyFilter(protocol);
for (auto combo : std::as_const(mSigningCombos)) {
auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
if (!widget) {
qCDebug(LIBKLEO_LOG) << "Failed to find signature combo widget";
continue;
}
widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol);
}
for (auto combo : std::as_const(mEncCombos)) {
auto widget = qobject_cast<ComboWidget *>(combo->parentWidget());
if (!widget) {
qCDebug(LIBKLEO_LOG) << "Failed to find combo widget";
continue;
}
widget->setVisible(protocol == GpgME::UnknownProtocol || widget->fixedProtocol() == GpgME::UnknownProtocol || widget->fixedProtocol() == protocol);
if (widget->isVisible() && combo->property("address") != mSender) {
combo->setKeyFilter(encryptionFilter);
}
}
// hide the labels indicating the protocol of the sender's keys if only a single protocol is active
const auto protocolLabels = q->findChildren<QLabel *>(QStringLiteral("protocol label"));
for (auto label : protocolLabels) {
label->setVisible(protocol == GpgME::UnknownProtocol);
}
}
auto createProtocolLabel(GpgME::Protocol protocol)
{
auto label = new QLabel(Formatting::displayName(protocol));
label->setObjectName(QStringLiteral("protocol label"));
return label;
}
ComboWidget *createSigningCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol protocol = GpgME::UnknownProtocol)
{
Q_ASSERT(!key.isNull() || protocol != UnknownProtocol);
protocol = !key.isNull() ? key.protocol() : protocol;
auto combo = new KeySelectionCombo();
auto comboWidget = new ComboWidget(combo);
#ifndef NDEBUG
combo->setObjectName(QStringLiteral("signing key"));
#endif
if (protocol == GpgME::OpenPGP) {
combo->setKeyFilter(s_pgpSignFilter);
} else if (protocol == GpgME::CMS) {
combo->setKeyFilter(s_smimeSignFilter);
}
if (key.isNull() || key_has_addr(key, mSender)) {
comboWidget->setIdFilter(mSender);
}
comboWidget->setFixedProtocol(protocol);
if (!key.isNull()) {
combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), protocol);
}
if (key.isNull() && protocol == OpenPGP) {
combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip);
}
combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")),
i18n("Don't confirm identity and integrity"),
IgnoreKey,
i18nc("@info:tooltip for not selecting a key for signing.", "The E-Mail will not be cryptographically signed."));
mSigningCombos << combo;
mAllCombos << combo;
combo->setProperty("address", addr);
connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() {
updateOkButton();
});
connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() {
updateOkButton();
});
return comboWidget;
}
void setSigningKeys(const std::vector<GpgME::Key> &preferredKeys, const std::vector<GpgME::Key> &alternativeKeys)
{
auto group = new QGroupBox(i18nc("Caption for signing key selection", "Confirm identity '%1' as:", mSender));
group->setAlignment(Qt::AlignLeft);
auto sigLayout = new QVBoxLayout(group);
const bool mayNeedOpenPGP = mForcedProtocol != CMS;
const bool mayNeedCMS = mForcedProtocol != OpenPGP;
if (mayNeedOpenPGP) {
if (mAllowMixed) {
sigLayout->addWidget(createProtocolLabel(OpenPGP));
}
const Key preferredKey = findfirstKeyOfType(preferredKeys, OpenPGP);
const Key alternativeKey = findfirstKeyOfType(alternativeKeys, OpenPGP);
if (!preferredKey.isNull()) {
qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
auto comboWidget = createSigningCombo(mSender, preferredKey);
sigLayout->addWidget(comboWidget);
} else if (!alternativeKey.isNull()) {
qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
auto comboWidget = createSigningCombo(mSender, alternativeKey);
sigLayout->addWidget(comboWidget);
} else {
qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for OpenPGP key";
auto comboWidget = createSigningCombo(mSender, Key(), OpenPGP);
sigLayout->addWidget(comboWidget);
}
}
if (mayNeedCMS) {
if (mAllowMixed) {
sigLayout->addWidget(createProtocolLabel(CMS));
}
const Key preferredKey = findfirstKeyOfType(preferredKeys, CMS);
const Key alternativeKey = findfirstKeyOfType(alternativeKeys, CMS);
if (!preferredKey.isNull()) {
qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << preferredKey;
auto comboWidget = createSigningCombo(mSender, preferredKey);
sigLayout->addWidget(comboWidget);
} else if (!alternativeKey.isNull()) {
qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for" << alternativeKey;
auto comboWidget = createSigningCombo(mSender, alternativeKey);
sigLayout->addWidget(comboWidget);
} else {
qCDebug(LIBKLEO_LOG) << "setSigningKeys - creating signing combo for S/MIME key";
auto comboWidget = createSigningCombo(mSender, Key(), CMS);
sigLayout->addWidget(comboWidget);
}
}
mScrollLayout->addWidget(group);
}
ComboWidget *createEncryptionCombo(const QString &addr, const GpgME::Key &key, GpgME::Protocol fixedProtocol)
{
auto combo = new KeySelectionCombo(false);
auto comboWidget = new ComboWidget(combo);
#ifndef NDEBUG
combo->setObjectName(QStringLiteral("encryption key"));
#endif
if (fixedProtocol == GpgME::OpenPGP) {
combo->setKeyFilter(s_pgpEncryptFilter);
} else if (fixedProtocol == GpgME::CMS) {
combo->setKeyFilter(s_smimeEncryptFilter);
} else {
combo->setKeyFilter(s_encryptFilter);
}
if (key.isNull() || key_has_addr(key, addr)) {
comboWidget->setIdFilter(addr);
}
comboWidget->setFixedProtocol(fixedProtocol);
if (!key.isNull()) {
combo->setDefaultKey(QString::fromLatin1(key.primaryFingerprint()), fixedProtocol);
}
if (addr == mSender && key.isNull() && fixedProtocol == OpenPGP) {
combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Generate a new key pair"), GenerateKey, mGenerateTooltip);
}
combo->appendCustomItem(QIcon::fromTheme(QStringLiteral("emblem-unavailable")),
i18n("No key. Recipient will be unable to decrypt."),
IgnoreKey,
i18nc("@info:tooltip for No Key selected for a specific recipient.",
"Do not select a key for this recipient.<br/><br/>"
"The recipient will receive the encrypted E-Mail, but it can only "
"be decrypted with the other keys selected in this dialog."));
connect(combo, &KeySelectionCombo::currentKeyChanged, q, [this]() {
updateOkButton();
});
connect(combo, qOverload<int>(&QComboBox::currentIndexChanged), q, [this]() {
updateOkButton();
});
mEncCombos << combo;
mAllCombos << combo;
combo->setProperty("address", addr);
return comboWidget;
}
void addEncryptionAddr(const QString &addr,
GpgME::Protocol preferredKeysProtocol,
const std::vector<GpgME::Key> &preferredKeys,
GpgME::Protocol alternativeKeysProtocol,
const std::vector<GpgME::Key> &alternativeKeys,
QGridLayout *encGrid)
{
if (addr == mSender) {
const bool mayNeedOpenPGP = mForcedProtocol != CMS;
const bool mayNeedCMS = mForcedProtocol != OpenPGP;
if (mayNeedOpenPGP) {
if (mAllowMixed) {
encGrid->addWidget(createProtocolLabel(OpenPGP), encGrid->rowCount(), 0);
}
for (const auto &key : preferredKeys) {
if (key.protocol() == OpenPGP) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
}
for (const auto &key : alternativeKeys) {
if (key.protocol() == OpenPGP) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
auto comboWidget = createEncryptionCombo(addr, key, OpenPGP);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
}
if (!anyKeyHasProtocol(preferredKeys, OpenPGP) && !anyKeyHasProtocol(alternativeKeys, OpenPGP)) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for OpenPGP key";
auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), OpenPGP);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
}
if (mayNeedCMS) {
if (mAllowMixed) {
encGrid->addWidget(createProtocolLabel(CMS), encGrid->rowCount(), 0);
}
for (const auto &key : preferredKeys) {
if (key.protocol() == CMS) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
auto comboWidget = createEncryptionCombo(addr, key, CMS);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
}
for (const auto &key : alternativeKeys) {
if (key.protocol() == CMS) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
auto comboWidget = createEncryptionCombo(addr, key, CMS);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
}
if (!anyKeyHasProtocol(preferredKeys, CMS) && !anyKeyHasProtocol(alternativeKeys, CMS)) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for S/MIME key";
auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), CMS);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
}
} else {
encGrid->addWidget(new QLabel(addr), encGrid->rowCount(), 0);
for (const auto &key : preferredKeys) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
auto comboWidget = createEncryptionCombo(addr, key, preferredKeysProtocol);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
for (const auto &key : alternativeKeys) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << key;
auto comboWidget = createEncryptionCombo(addr, key, alternativeKeysProtocol);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
if (!mAllowMixed) {
if (preferredKeys.empty()) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for" << Formatting::displayName(preferredKeysProtocol)
<< "key";
auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), preferredKeysProtocol);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
if (alternativeKeys.empty() && alternativeKeysProtocol != UnknownProtocol) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for"
<< Formatting::displayName(alternativeKeysProtocol) << "key";
auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), alternativeKeysProtocol);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
} else {
if (preferredKeys.empty() && alternativeKeys.empty()) {
qCDebug(LIBKLEO_LOG) << "setEncryptionKeys -" << addr << "- creating encryption combo for any key";
auto comboWidget = createEncryptionCombo(addr, GpgME::Key(), UnknownProtocol);
encGrid->addWidget(comboWidget, encGrid->rowCount(), 0, 1, 2);
}
}
}
}
void setEncryptionKeys(GpgME::Protocol preferredKeysProtocol,
const QMap<QString, std::vector<GpgME::Key>> &preferredKeys,
GpgME::Protocol alternativeKeysProtocol,
const QMap<QString, std::vector<GpgME::Key>> &alternativeKeys)
{
{
auto group = new QGroupBox(i18nc("Encrypt to self (email address):", "Encrypt to self (%1):", mSender));
#ifndef NDEBUG
group->setObjectName(QStringLiteral("encrypt-to-self box"));
#endif
group->setAlignment(Qt::AlignLeft);
auto encGrid = new QGridLayout(group);
addEncryptionAddr(mSender, preferredKeysProtocol, preferredKeys.value(mSender), alternativeKeysProtocol, alternativeKeys.value(mSender), encGrid);
encGrid->setColumnStretch(1, -1);
mScrollLayout->addWidget(group);
}
const bool hasOtherRecipients = std::any_of(preferredKeys.keyBegin(), preferredKeys.keyEnd(), [this](const auto &recipient) {
return recipient != mSender;
});
if (hasOtherRecipients) {
auto group = new QGroupBox(i18n("Encrypt to others:"));
#ifndef NDEBUG
group->setObjectName(QStringLiteral("encrypt-to-others box"));
#endif
group->setAlignment(Qt::AlignLeft);
auto encGrid = new QGridLayout{group};
for (auto it = std::begin(preferredKeys); it != std::end(preferredKeys); ++it) {
const auto &address = it.key();
const auto &keys = it.value();
if (address != mSender) {
addEncryptionAddr(address, preferredKeysProtocol, keys, alternativeKeysProtocol, alternativeKeys.value(address), encGrid);
}
}
encGrid->setColumnStretch(1, -1);
mScrollLayout->addWidget(group);
}
mScrollLayout->addStretch(-1);
}
void updateOkButton()
{
static QString origOkText = mOkButton->text();
const bool isGenerate = bool(findVisibleKeySelectionComboWithGenerateKey());
const bool allVisibleEncryptionKeysAreIgnored = std::all_of(std::begin(mEncCombos), std::end(mEncCombos), [](auto combo) {
return !combo->isVisible() || combo->currentData(Qt::UserRole).toInt() == IgnoreKey;
});
// If we don't encrypt the ok button is always enabled. But otherwise
// we only enable it if we encrypt to at least one recipient.
mOkButton->setEnabled(!mEncrypt || !allVisibleEncryptionKeysAreIgnored);
mOkButton->setText(isGenerate ? i18n("Generate") : origOkText);
if (!Kleo::gnupgUsesDeVsCompliance()) {
return;
}
// Handle compliance
bool de_vs = Kleo::gnupgIsDeVsCompliant();
if (de_vs) {
const GpgME::Protocol protocol = currentProtocol();
for (const auto combo : std::as_const(mAllCombos)) {
if (!combo->isVisible()) {
continue;
}
const auto key = combo->currentKey();
if (key.isNull()) {
continue;
}
if (protocol != GpgME::UnknownProtocol && key.protocol() != protocol) {
continue;
}
if (!Formatting::isKeyDeVs(key) || keyValidity(key) < GpgME::UserID::Validity::Full) {
de_vs = false;
break;
}
}
}
if (de_vs) {
mOkButton->setIcon(QIcon::fromTheme(QStringLiteral("security-high")));
mOkButton->setStyleSheet(
QStringLiteral("background-color: ")
+ QStringLiteral(
"#D5FAE2")); // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name());
mComplianceLbl->setText(i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"%1 communication possible.",
Formatting::deVsString()));
} else {
mOkButton->setIcon(QIcon::fromTheme(QStringLiteral("security-medium")));
mOkButton->setStyleSheet(
QStringLiteral("background-color: ")
+ QStringLiteral(
"#FAE9EB")); // KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name()));
mComplianceLbl->setText(i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"%1 communication not possible.",
Formatting::deVsString()));
}
mComplianceLbl->setVisible(true);
}
GpgME::Protocol mForcedProtocol;
QList<KeySelectionCombo *> mSigningCombos;
QList<KeySelectionCombo *> mEncCombos;
QList<KeySelectionCombo *> mAllCombos;
QScrollArea *mScrollArea;
QVBoxLayout *mScrollLayout;
QPushButton *mOkButton;
QVBoxLayout *mMainLay;
QButtonGroup *mFormatBtns;
QString mSender;
bool mSign;
bool mEncrypt;
bool mAllowMixed;
NewKeyApprovalDialog *q;
QList<QGpgME::Job *> mRunningJobs;
GpgME::Error mLastError;
QLabel *mComplianceLbl;
KeyResolver::Solution mAcceptedResult;
QString mGenerateTooltip;
};
NewKeyApprovalDialog::NewKeyApprovalDialog(bool encrypt,
bool sign,
const QString &sender,
KeyResolver::Solution preferredSolution,
KeyResolver::Solution alternativeSolution,
bool allowMixed,
GpgME::Protocol forcedProtocol,
QWidget *parent,
Qt::WindowFlags f)
: QDialog(parent, f)
, d{std::make_unique<Private>(this, encrypt, sign, forcedProtocol, preferredSolution.protocol, sender, allowMixed)}
{
if (sign) {
d->setSigningKeys(std::move(preferredSolution.signingKeys), std::move(alternativeSolution.signingKeys));
}
if (encrypt) {
d->setEncryptionKeys(allowMixed ? UnknownProtocol : preferredSolution.protocol,
std::move(preferredSolution.encryptionKeys),
allowMixed ? UnknownProtocol : alternativeSolution.protocol,
std::move(alternativeSolution.encryptionKeys));
}
d->updateWidgets();
d->updateOkButton();
const auto size = sizeHint();
const auto desk = screen()->size();
resize(QSize(desk.width() / 3, qMin(size.height(), desk.height() / 2)));
}
Kleo::NewKeyApprovalDialog::~NewKeyApprovalDialog() = default;
KeyResolver::Solution NewKeyApprovalDialog::result()
{
return d->mAcceptedResult;
}
#include "newkeyapprovaldialog.moc"
diff --git a/src/ui/progressbar.cpp b/src/ui/progressbar.cpp
index 4eaa4ce5..4bfba0f6 100644
--- a/src/ui/progressbar.cpp
+++ b/src/ui/progressbar.cpp
@@ -1,97 +1,98 @@
/*
progressbar.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "progressbar.h"
-#include "kleo_ui_debug.h"
+#include <kleo_ui_debug.h>
+
#include <QTimer>
static const int busyTimerTickInterval = 100;
static const int busyTimerTickIncrement = 5;
Kleo::ProgressBar::ProgressBar(QWidget *parent)
: QProgressBar(parent)
, mRealProgress(-1)
{
mBusyTimer = new QTimer(this);
connect(mBusyTimer, &QTimer::timeout, this, &ProgressBar::slotBusyTimerTick);
fixup(true);
}
void Kleo::ProgressBar::slotProgress(const QString &, int cur, int tot)
{
setRange(cur, tot);
}
void Kleo::ProgressBar::slotProgress(const QString &, int, int cur, int tot)
{
setRange(cur, tot);
}
void Kleo::ProgressBar::setMaximum(int total)
{
qCDebug(KLEO_UI_LOG) << "Kleo::ProgressBar::setMaximum(" << total << " )";
if (total == maximum()) {
return;
}
QProgressBar::setMaximum(0);
fixup(false);
}
void Kleo::ProgressBar::setValue(int p)
{
qCDebug(KLEO_UI_LOG) << "Kleo::ProgressBar::setValue(" << p << " )";
mRealProgress = p;
fixup(true);
}
void Kleo::ProgressBar::reset()
{
mRealProgress = -1;
fixup(true);
}
void Kleo::ProgressBar::slotBusyTimerTick()
{
fixup(false);
if (mBusyTimer->isActive()) {
QProgressBar::setValue(QProgressBar::value() + busyTimerTickIncrement);
}
}
void Kleo::ProgressBar::fixup(bool newValue)
{
const int cur = QProgressBar::value();
const int tot = QProgressBar::maximum();
qCDebug(KLEO_UI_LOG) << "Kleo::ProgressBar::startStopBusyTimer() cur =" << cur << "; tot =" << tot << "; real =" << mRealProgress;
if ((newValue && mRealProgress < 0) || (!newValue && cur < 0)) {
qCDebug(KLEO_UI_LOG) << "(new value) switch to reset";
mBusyTimer->stop();
if (newValue) {
QProgressBar::reset();
}
mRealProgress = -1;
} else if (tot == 0) {
qCDebug(KLEO_UI_LOG) << "(new value) switch or stay in busy";
if (!mBusyTimer->isActive()) {
mBusyTimer->start(busyTimerTickInterval);
if (newValue) {
QProgressBar::setValue(mRealProgress);
}
}
} else {
qCDebug(KLEO_UI_LOG) << "(new value) normal progress";
mBusyTimer->stop();
if (QProgressBar::value() != mRealProgress) {
QProgressBar::setValue(mRealProgress);
}
}
}
diff --git a/src/ui/progressdialog.cpp b/src/ui/progressdialog.cpp
index ee5cd84f..65f9ef4c 100644
--- a/src/ui/progressdialog.cpp
+++ b/src/ui/progressdialog.cpp
@@ -1,74 +1,75 @@
/*
progressdialog.cpp
This file is part of libkleopatra, the KDE keymanagement library
SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "progressdialog.h"
-#include "kleo_ui_debug.h"
#ifndef QT_NO_PROGRESSDIALOG
#include "progressbar.h"
+#include <kleo_ui_debug.h>
+
#include <KLocalizedString>
#include <QTimer>
Kleo::ProgressDialog::ProgressDialog(QGpgME::Job *job, const QString &baseText, QWidget *creator, Qt::WindowFlags f)
: QProgressDialog(creator, f)
, mBaseText(baseText)
{
Q_ASSERT(job);
setBar(new ProgressBar(this /*, "replacement progressbar in Kleo::ProgressDialog"*/));
setMinimumDuration(2000 /*ms*/);
setAutoReset(false);
setAutoClose(false);
setLabelText(baseText);
setModal(false);
setRange(0, 0); // activate busy indicator
connect(job, &QGpgME::Job::progress, this, &ProgressDialog::slotProgress);
connect(job, &QGpgME::Job::done, this, &ProgressDialog::slotDone);
connect(this, &QProgressDialog::canceled, job, &QGpgME::Job::slotCancel);
QTimer::singleShot(minimumDuration(), this, &ProgressDialog::forceShow);
}
Kleo::ProgressDialog::~ProgressDialog()
{
}
void Kleo::ProgressDialog::setMinimumDuration(int ms)
{
if (0 < ms && ms < minimumDuration()) {
QTimer::singleShot(ms, this, &ProgressDialog::forceShow);
}
QProgressDialog::setMinimumDuration(ms);
}
void Kleo::ProgressDialog::slotProgress(const QString &what, int current, int total)
{
qCDebug(KLEO_UI_LOG) << "Kleo::ProgressDialog::slotProgress( \"" << what << "\"," << current << "," << total << ")";
if (mBaseText.isEmpty()) {
setLabelText(what);
} else if (what.isEmpty()) {
setLabelText(mBaseText);
} else {
setLabelText(i18n("%1: %2", mBaseText, what));
}
setRange(current, total);
}
void Kleo::ProgressDialog::slotDone()
{
qCDebug(KLEO_UI_LOG) << "Kleo::ProgressDialog::slotDone()";
hide();
deleteLater();
}
#endif // QT_NO_PROGRESSDIALOG
diff --git a/src/ui/readerportselection.cpp b/src/ui/readerportselection.cpp
index e394786f..fc36aade 100644
--- a/src/ui/readerportselection.cpp
+++ b/src/ui/readerportselection.cpp
@@ -1,144 +1,144 @@
/*
ui/readerportselection.cpp
This file is part of libkleopatra
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "readerportselection.h"
#include "utils/scdaemon.h"
+#include <libkleo_debug.h>
+
#include <KLocalizedString>
#if __has_include(<QGpgME/Debug>)
#include <QGpgME/Debug>
#endif
#include <QComboBox>
#include <QHBoxLayout>
#include <QLineEdit>
#include <gpgme++/error.h>
-#include <libkleo_debug.h>
-
using namespace Kleo;
class ReaderPortSelection::Private
{
public:
Private(ReaderPortSelection *q);
void setValue(const QString &value);
QString value() const;
private:
void onCurrentIndexChanged(int);
void onEditTextChanged(const QString &);
private:
ReaderPortSelection *const q = nullptr;
QComboBox *const mComboBox = nullptr;
};
ReaderPortSelection::Private::Private(Kleo::ReaderPortSelection *qq)
: q{qq}
, mComboBox{new QComboBox{qq}}
{
auto layout = new QHBoxLayout{q};
layout->setContentsMargins({});
layout->addWidget(mComboBox);
mComboBox->addItem(i18nc("@item:inlistbox", "Default reader"), {});
GpgME::Error err;
const auto readers = SCDaemon::getReaders(err);
if (err) {
qCWarning(LIBKLEO_LOG) << "Getting available smart card readers failed:" << err;
} else {
std::for_each(std::begin(readers), std::end(readers), [this](const auto &reader) {
const auto readerId = QString::fromStdString(reader);
mComboBox->addItem(readerId, readerId);
});
}
mComboBox->addItem(QString{}, {});
mComboBox->setToolTip(xi18nc("@info:tooltip",
"<para>Select the smart card reader that GnuPG shall use.<list>"
"<item>The first item will make GnuPG use the first reader that is found.</item>"
"<item>The last item allows you to enter a custom reader ID or reader port number.</item>"
"<item>All other items represent readers that were found by GnuPG.</item>"
"</list></para>"));
connect(mComboBox, qOverload<int>(&QComboBox::currentIndexChanged), q, [this](int index) {
onCurrentIndexChanged(index);
Q_EMIT q->valueChanged(q->value());
});
connect(mComboBox, &QComboBox::editTextChanged, q, [this](const QString &text) {
onEditTextChanged(text);
Q_EMIT q->valueChanged(q->value());
});
}
void ReaderPortSelection::Private::setValue(const QString &value)
{
if (value.isEmpty()) {
mComboBox->setCurrentIndex(0);
return;
}
const int indexOfValue = mComboBox->findData(value);
if (indexOfValue != -1) {
mComboBox->setCurrentIndex(indexOfValue);
} else {
mComboBox->setCurrentIndex(mComboBox->count() - 1);
mComboBox->setEditText(value);
}
}
QString ReaderPortSelection::Private::value() const
{
return mComboBox->currentData().toString();
}
void ReaderPortSelection::Private::onCurrentIndexChanged(int index)
{
// the last item serves as input for a custom entry
mComboBox->setEditable(index == mComboBox->count() - 1);
if (mComboBox->lineEdit()) {
mComboBox->lineEdit()->setPlaceholderText(i18nc("@item:inlistbox", "Custom reader ID or port number"));
}
}
void ReaderPortSelection::Private::onEditTextChanged(const QString &text)
{
const int lastIndex = mComboBox->count() - 1;
// do not overwrite the text of the custom item with the text of another item
if (mComboBox->currentIndex() == lastIndex) {
mComboBox->setItemText(lastIndex, text);
mComboBox->setItemData(lastIndex, text);
}
}
ReaderPortSelection::ReaderPortSelection(QWidget *parent)
: QWidget{parent}
, d{new Private{this}}
{
}
ReaderPortSelection::~ReaderPortSelection() = default;
void ReaderPortSelection::setValue(const QString &value)
{
d->setValue(value);
}
QString ReaderPortSelection::value() const
{
return d->value();
}
diff --git a/src/utils/assuan.cpp b/src/utils/assuan.cpp
index 358bd5d0..876f8abe 100644
--- a/src/utils/assuan.cpp
+++ b/src/utils/assuan.cpp
@@ -1,150 +1,150 @@
/*
utils/assuan.cpp
This file is part of libkleopatra
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "assuan.h"
+#include <libkleo_debug.h>
+
#include <QThread>
#if __has_include(<QGpgME/Debug>)
#include <QGpgME/Debug>
#endif
#include <gpgme++/context.h>
#include <gpgme++/defaultassuantransaction.h>
#include <gpgme++/error.h>
-#include "libkleo_debug.h"
-
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Assuan;
using namespace std::chrono_literals;
static const auto initialRetryDelay = 125ms;
static const auto maxRetryDelay = 1000ms;
static const auto maxConnectionAttempts = 10;
namespace
{
static QDebug operator<<(QDebug s, const std::string &string)
{
return s << QString::fromStdString(string);
}
static QDebug operator<<(QDebug s, const std::vector<std::pair<std::string, std::string>> &v)
{
using pair = std::pair<std::string, std::string>;
s << '(';
for (const pair &p : v) {
s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n';
}
return s << ')';
}
}
bool Kleo::Assuan::agentIsRunning()
{
Error err;
const std::unique_ptr<Context> ctx = Context::createForEngine(AssuanEngine, &err);
if (err) {
qCWarning(LIBKLEO_LOG) << __func__ << ": Creating context for Assuan engine failed:" << err;
return false;
}
static const char *command = "GETINFO version";
err = ctx->assuanTransact(command);
if (!err) {
// all good
} else if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) {
qCDebug(LIBKLEO_LOG) << __func__ << ": Connecting to the agent failed.";
} else {
qCWarning(LIBKLEO_LOG) << __func__ << ": Starting Assuan transaction for" << command << "failed:" << err;
}
return !err;
}
std::unique_ptr<GpgME::AssuanTransaction> Kleo::Assuan::sendCommand(std::shared_ptr<GpgME::Context> &context,
const std::string &command,
std::unique_ptr<GpgME::AssuanTransaction> transaction,
GpgME::Error &err)
{
qCDebug(LIBKLEO_LOG) << __func__ << command;
int connectionAttempts = 1;
err = context->assuanTransact(command.c_str(), std::move(transaction));
auto retryDelay = initialRetryDelay;
while (err.code() == GPG_ERR_ASS_CONNECT_FAILED && connectionAttempts < maxConnectionAttempts) {
// Esp. on Windows the agent processes may take their time so we try
// in increasing waits for them to start up
qCDebug(LIBKLEO_LOG) << "Connecting to the agent failed. Retrying in" << retryDelay.count() << "ms";
QThread::msleep(retryDelay.count());
retryDelay = std::min(retryDelay * 2, maxRetryDelay);
connectionAttempts++;
err = context->assuanTransact(command.c_str(), context->takeLastAssuanTransaction());
}
if (err.code()) {
qCDebug(LIBKLEO_LOG) << __func__ << command << "failed:" << err;
if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) {
qCDebug(LIBKLEO_LOG) << "Assuan problem, killing context";
context.reset();
}
return {};
}
return context->takeLastAssuanTransaction();
}
std::unique_ptr<DefaultAssuanTransaction> Kleo::Assuan::sendCommand(std::shared_ptr<Context> &context, const std::string &command, Error &err)
{
std::unique_ptr<AssuanTransaction> t = sendCommand(context, command, std::make_unique<DefaultAssuanTransaction>(), err);
return std::unique_ptr<DefaultAssuanTransaction>(dynamic_cast<DefaultAssuanTransaction *>(t.release()));
}
std::string Kleo::Assuan::sendDataCommand(std::shared_ptr<Context> context, const std::string &command, Error &err)
{
std::string data;
const std::unique_ptr<DefaultAssuanTransaction> t = sendCommand(context, command, err);
if (t.get()) {
data = t->data();
qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << QString::fromStdString(data);
} else {
qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL";
}
return data;
}
std::vector<std::pair<std::string, std::string>> Kleo::Assuan::sendStatusLinesCommand(std::shared_ptr<Context> context, const std::string &command, Error &err)
{
std::vector<std::pair<std::string, std::string>> statusLines;
const std::unique_ptr<DefaultAssuanTransaction> t = sendCommand(context, command, err);
if (t.get()) {
statusLines = t->statusLines();
qCDebug(LIBKLEO_LOG) << __func__ << command << ": got" << statusLines;
} else {
qCDebug(LIBKLEO_LOG) << __func__ << command << ": t == NULL";
}
return statusLines;
}
std::string Kleo::Assuan::sendStatusCommand(const std::shared_ptr<Context> &context, const std::string &command, Error &err)
{
const auto lines = sendStatusLinesCommand(context, command, err);
// The status is only the last attribute
// e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO
// it would only be FOO
const auto lastSpace = command.rfind(' ');
const auto needle = lastSpace == std::string::npos ? command : command.substr(lastSpace + 1);
for (const auto &pair : lines) {
if (pair.first == needle) {
return pair.second;
}
}
return {};
}
diff --git a/src/utils/classify.cpp b/src/utils/classify.cpp
index 98ca8566..a08f04db 100644
--- a/src/utils/classify.cpp
+++ b/src/utils/classify.cpp
@@ -1,421 +1,422 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/classify.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "classify.h"
#include "kleo/checksumdefinition.h"
-#include "libkleo_debug.h"
#include "utils/algorithm.h"
+#include <libkleo_debug.h>
+
#include <QByteArrayMatcher>
#include <QFile>
#include <QFileInfo>
#include <QMap>
#include <QRegExp>
#include <QRegularExpression>
#include <QString>
#include <gpgme++/data.h>
#include <qgpgme/dataprovider.h>
#include <functional>
#include <iterator>
using namespace Kleo::Class;
namespace
{
const unsigned int ExamineContentHint = 0x8000;
static const struct _classification {
char extension[4];
unsigned int classification;
} classifications[] = {
// ordered by extension
{"arl", Kleo::Class::CMS | Binary | CertificateRevocationList},
{"asc", Kleo::Class::OpenPGP | Ascii | OpaqueSignature | DetachedSignature | CipherText | AnyCertStoreType | ExamineContentHint},
{"cer", Kleo::Class::CMS | Binary | Certificate},
{"crl", Kleo::Class::CMS | Binary | CertificateRevocationList},
{"crt", Kleo::Class::CMS | Binary | Certificate},
{"der", Kleo::Class::CMS | Binary | Certificate | CertificateRevocationList},
{"gpg", Kleo::Class::OpenPGP | Binary | OpaqueSignature | CipherText | AnyCertStoreType | ExamineContentHint},
{"p10", Kleo::Class::CMS | Ascii | CertificateRequest},
{"p12", Kleo::Class::CMS | Binary | ExportedPSM},
{"p7c", Kleo::Class::CMS | Binary | Certificate},
{"p7m", Kleo::Class::CMS | AnyFormat | CipherText},
{"p7s", Kleo::Class::CMS | AnyFormat | AnySignature},
{"pem", Kleo::Class::CMS | Ascii | AnyType | ExamineContentHint},
{"pfx", Kleo::Class::CMS | Binary | Certificate},
{"pgp", Kleo::Class::OpenPGP | Binary | OpaqueSignature | CipherText | AnyCertStoreType | ExamineContentHint},
{"sig", Kleo::Class::OpenPGP | AnyFormat | DetachedSignature},
};
static const QMap<GpgME::Data::Type, unsigned int> gpgmeTypeMap{
// clang-format off
{GpgME::Data::PGPSigned, Kleo::Class::OpenPGP | OpaqueSignature },
/* PGPOther might be just an unencrypted unsigned pgp message. Decrypt
* would yield the plaintext anyway so for us this is CipherText. */
{GpgME::Data::PGPOther, Kleo::Class::OpenPGP | CipherText },
{GpgME::Data::PGPKey, Kleo::Class::OpenPGP | Certificate },
{GpgME::Data::CMSSigned, Kleo::Class::CMS | AnySignature },
{GpgME::Data::CMSEncrypted, Kleo::Class::CMS | CipherText },
/* See PGPOther */
{GpgME::Data::CMSOther, Kleo::Class::CMS | CipherText },
{GpgME::Data::X509Cert, Kleo::Class::CMS | Certificate },
{GpgME::Data::PKCS12, Kleo::Class::CMS | Binary | ExportedPSM },
{GpgME::Data::PGPEncrypted, Kleo::Class::OpenPGP | CipherText },
{GpgME::Data::PGPSignature, Kleo::Class::OpenPGP | DetachedSignature},
// clang-format on
};
static const unsigned int defaultClassification = NoClass;
template<template<typename U> class Op>
struct ByExtension {
using result_type = bool;
template<typename T>
bool operator()(const T &lhs, const T &rhs) const
{
return Op<int>()(qstricmp(lhs.extension, rhs.extension), 0);
}
template<typename T>
bool operator()(const T &lhs, const char *rhs) const
{
return Op<int>()(qstricmp(lhs.extension, rhs), 0);
}
template<typename T>
bool operator()(const char *lhs, const T &rhs) const
{
return Op<int>()(qstricmp(lhs, rhs.extension), 0);
}
bool operator()(const char *lhs, const char *rhs) const
{
return Op<int>()(qstricmp(lhs, rhs), 0);
}
};
static const struct _content_classification {
char content[28];
unsigned int classification;
} content_classifications[] = {
// clang-format off
{"CERTIFICATE", Certificate },
{"ENCRYPTED MESSAGE", CipherText },
{"MESSAGE", OpaqueSignature | CipherText },
{"PKCS12", ExportedPSM },
{"PRIVATE KEY BLOCK", ExportedPSM },
{"PUBLIC KEY BLOCK", Certificate },
{"SIGNATURE", DetachedSignature },
{"SIGNED MESSAGE", ClearsignedMessage | DetachedSignature},
// clang-format on
};
template<template<typename U> class Op>
struct ByContent {
using result_type = bool;
const unsigned int N;
explicit ByContent(unsigned int n)
: N(n)
{
}
template<typename T>
bool operator()(const T &lhs, const T &rhs) const
{
return Op<int>()(qstrncmp(lhs.content, rhs.content, N), 0);
}
template<typename T>
bool operator()(const T &lhs, const char *rhs) const
{
return Op<int>()(qstrncmp(lhs.content, rhs, N), 0);
}
template<typename T>
bool operator()(const char *lhs, const T &rhs) const
{
return Op<int>()(qstrncmp(lhs, rhs.content, N), 0);
}
bool operator()(const char *lhs, const char *rhs) const
{
return Op<int>()(qstrncmp(lhs, rhs, N), 0);
}
};
}
unsigned int Kleo::classify(const QStringList &fileNames)
{
if (fileNames.empty()) {
return 0;
}
unsigned int result = classify(fileNames.front());
for (const QString &fileName : fileNames) {
result &= classify(fileName);
}
return result;
}
static unsigned int classifyExtension(const QFileInfo &fi)
{
const _classification *const it =
Kleo::binary_find(std::begin(classifications), std::end(classifications), fi.suffix().toLatin1().constData(), ByExtension<std::less>());
if (it != std::end(classifications)) {
if (!(it->classification & ExamineContentHint)) {
return it->classification;
}
}
return it == std::end(classifications) ? defaultClassification : it->classification;
}
unsigned int Kleo::classify(const QString &filename)
{
Q_ASSERT(std::is_sorted(std::begin(classifications), std::end(classifications), ByExtension<std::less>()));
const QFileInfo fi(filename);
if (!fi.exists()) {
return 0;
}
QFile file(filename);
/* The least reliable but always available classification */
const unsigned int extClass = classifyExtension(fi);
if (!GpgME::hasFeature(0, GpgME::BinaryAndFineGrainedIdentify) && !(extClass & ExamineContentHint)) {
/* GpgME's identify and our internal Classify were so incomplete
* before BinaryAndFineGrainedIdentify that we are better of
* to just use the file extension if ExamineContentHint is not set. */
qCDebug(LIBKLEO_LOG) << "Classified based only on extension.";
return extClass;
}
if (!file.open(QIODevice::ReadOnly)) {
qCDebug(LIBKLEO_LOG) << "Failed to open file: " << filename << " for classification.";
return extClass;
}
/* More reliable */
const unsigned int contentClass = classifyContent(file.read(4096));
if (contentClass != defaultClassification) {
qCDebug(LIBKLEO_LOG) << "Classified based on content as:" << contentClass;
return contentClass;
}
/* Probably some X509 Stuff that GpgME in it's wisdom does not handle. Again
* file extension is probably more reliable as the last resort. */
qCDebug(LIBKLEO_LOG) << "No classification based on content.";
return extClass;
}
static unsigned int classifyContentInteral(const QByteArray &data)
{
Q_ASSERT(std::is_sorted(std::begin(content_classifications), std::end(content_classifications), ByContent<std::less>(100)));
static const char beginString[] = "-----BEGIN ";
static const QByteArrayMatcher beginMatcher(beginString);
int pos = beginMatcher.indexIn(data);
if (pos < 0) {
return defaultClassification;
}
pos += sizeof beginString - 1;
const bool pgp = qstrncmp(data.data() + pos, "PGP ", 4) == 0;
if (pgp) {
pos += 4;
}
const int epos = data.indexOf("-----\n", pos);
if (epos < 0) {
return defaultClassification;
}
const _content_classification *const cit =
Kleo::binary_find(std::begin(content_classifications), std::end(content_classifications), data.data() + pos, ByContent<std::less>(epos - pos));
if (cit != std::end(content_classifications)) {
return cit->classification | (pgp ? Kleo::Class::OpenPGP : Kleo::Class::CMS);
}
return defaultClassification;
}
unsigned int Kleo::classifyContent(const QByteArray &data)
{
/* As of Version 1.6.0 GpgME does not distinguish between detached
* signatures and signatures. So we prefer kleo's classification and
* only use gpgme as fallback.
* With newer versions we have a better identify that really inspects
* the PGP Packages. Which is by far the most reliable classification.
* So this is already used for the default classification. File extensions
* and our classifyinternal is only used as a fallback.
*/
if (!GpgME::hasFeature(0, GpgME::BinaryAndFineGrainedIdentify)) {
unsigned int ourClassification = classifyContentInteral(data);
if (ourClassification != defaultClassification) {
return ourClassification;
}
}
QGpgME::QByteArrayDataProvider dp(data);
GpgME::Data gpgmeData(&dp);
GpgME::Data::Type type = gpgmeData.type();
return gpgmeTypeMap.value(type, defaultClassification);
}
QString Kleo::printableClassification(unsigned int classification)
{
QStringList parts;
if (classification & Kleo::Class::CMS) {
parts.push_back(QStringLiteral("CMS"));
}
if (classification & Kleo::Class::OpenPGP) {
parts.push_back(QStringLiteral("OpenPGP"));
}
if (classification & Kleo::Class::Binary) {
parts.push_back(QStringLiteral("Binary"));
}
if (classification & Kleo::Class::Ascii) {
parts.push_back(QStringLiteral("Ascii"));
}
if (classification & Kleo::Class::DetachedSignature) {
parts.push_back(QStringLiteral("DetachedSignature"));
}
if (classification & Kleo::Class::OpaqueSignature) {
parts.push_back(QStringLiteral("OpaqueSignature"));
}
if (classification & Kleo::Class::ClearsignedMessage) {
parts.push_back(QStringLiteral("ClearsignedMessage"));
}
if (classification & Kleo::Class::CipherText) {
parts.push_back(QStringLiteral("CipherText"));
}
if (classification & Kleo::Class::Certificate) {
parts.push_back(QStringLiteral("Certificate"));
}
if (classification & Kleo::Class::ExportedPSM) {
parts.push_back(QStringLiteral("ExportedPSM"));
}
if (classification & Kleo::Class::CertificateRequest) {
parts.push_back(QStringLiteral("CertificateRequest"));
}
return parts.join(QLatin1String(", "));
}
static QString chopped(QString s, unsigned int n)
{
s.chop(n);
return s;
}
/*!
\return the data file that corresponds to the signature file \a
signatureFileName, or QString(), if no such file can be found.
*/
QString Kleo::findSignedData(const QString &signatureFileName)
{
if (!mayBeDetachedSignature(signatureFileName)) {
return QString();
}
const QString baseName = chopped(signatureFileName, 4);
return QFile::exists(baseName) ? baseName : QString();
}
/*!
\return all (existing) candidate signature files for \a signedDataFileName
Note that there can very well be more than one such file, e.g. if
the same data file was signed by both CMS and OpenPGP certificates.
*/
QStringList Kleo::findSignatures(const QString &signedDataFileName)
{
QStringList result;
for (unsigned int i = 0, end = sizeof(classifications) / sizeof(_classification); i < end; ++i) {
if (classifications[i].classification & DetachedSignature) {
const QString candidate = signedDataFileName + QLatin1Char('.') + QLatin1String(classifications[i].extension);
if (QFile::exists(candidate)) {
result.push_back(candidate);
}
}
}
return result;
}
/*!
\return the (likely) output filename for \a inputFileName, or
"inputFileName.out" if none can be determined.
*/
QString Kleo::outputFileName(const QString &inputFileName)
{
const QFileInfo fi(inputFileName);
if (!std::binary_search(std::begin(classifications), std::end(classifications), fi.suffix().toLatin1().constData(), ByExtension<std::less>())) {
return inputFileName + QLatin1String(".out");
} else {
return chopped(inputFileName, 4);
}
}
/*!
\return the commonly used extension for files of type
\a classification, or NULL if none such exists.
*/
const char *Kleo::outputFileExtension(unsigned int classification, bool usePGPFileExt)
{
if (usePGPFileExt && (classification & Class::OpenPGP) && (classification & Class::Binary)) {
return "pgp";
}
for (unsigned int i = 0; i < sizeof classifications / sizeof *classifications; ++i) {
if ((classifications[i].classification & classification) == classification) {
return classifications[i].extension;
}
}
return nullptr;
}
bool Kleo::isFingerprint(const QString &fpr)
{
static QRegularExpression fprRegex(QStringLiteral("[0-9a-fA-F]{40}"));
return fprRegex.match(fpr).hasMatch();
}
bool Kleo::isChecksumFile(const QString &file)
{
static bool initialized;
static QList<QRegExp> patterns;
const QFileInfo fi(file);
if (!fi.exists()) {
return false;
}
if (!initialized) {
const auto getChecksumDefinitions = ChecksumDefinition::getChecksumDefinitions();
for (const std::shared_ptr<ChecksumDefinition> &cd : getChecksumDefinitions) {
if (cd) {
const auto patternsList = cd->patterns();
for (const QString &pattern : patternsList) {
#ifdef Q_OS_WIN
patterns << QRegExp(pattern, Qt::CaseInsensitive);
#else
patterns << QRegExp(pattern, Qt::CaseSensitive);
#endif
}
}
}
initialized = true;
}
const QString fileName = fi.fileName();
for (const QRegExp &pattern : std::as_const(patterns)) {
if (pattern.exactMatch(fileName)) {
return true;
}
}
return false;
}
diff --git a/src/utils/gnupg.cpp b/src/utils/gnupg.cpp
index bcb64e49..06039716 100644
--- a/src/utils/gnupg.cpp
+++ b/src/utils/gnupg.cpp
@@ -1,684 +1,684 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/gnupg.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2020-2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "gnupg.h"
#include "utils/assuan.h"
#include "utils/compat.h"
#include "utils/cryptoconfig.h"
#include "utils/hex.h"
+#include <libkleo_debug.h>
+
#include <gpgme++/engineinfo.h>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
-#include "libkleo_debug.h"
-
#include <QByteArray>
#include <QCoreApplication>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QPointer>
#include <QProcess>
#include <QRegExp>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QString>
#include <gpg-error.h>
#ifdef Q_OS_WIN
#include "gnupg-registry.h"
#endif // Q_OS_WIN
#include <algorithm>
#include <array>
#include <KLocalizedString>
using namespace GpgME;
QString Kleo::gnupgHomeDirectory()
{
static const QString homeDir = QString::fromUtf8(GpgME::dirInfo("homedir"));
return homeDir;
}
int Kleo::makeGnuPGError(int code)
{
return gpg_error(static_cast<gpg_err_code_t>(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 {
// 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"),
// Secret keys (living under private-keys-v1.d/)
QStringLiteral("*.key"),
// 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-?.?"),
};
}
QStringList Kleo::gnupgFolderWhitelist()
{
static const QDir gnupgHome{gnupgHomeDirectory()};
return {
gnupgHome.path(),
gnupgHome.filePath(QStringLiteral("private-keys-v1.d")),
};
}
namespace
{
class Gpg4win
{
public:
static const Gpg4win *instance()
{
// We use singleton to do the signature check only once.
static Gpg4win *inst = nullptr;
if (!inst) {
inst = new Gpg4win();
}
return inst;
}
private:
QString mVersion;
QString mDescription;
QString mDescLong;
bool mSignedVersion;
Gpg4win()
: mVersion(QStringLiteral("Unknown Windows Version"))
, mDescription(i18n("Certificate Manager and Unified Crypto GUI"))
, mDescLong(QStringLiteral("<a href=https://www.gpg4win.org>Visit the Gpg4win homepage</a>"))
, mSignedVersion(false)
{
const QString instPath = Kleo::gpg4winInstallPath();
const QString verPath = instPath + QStringLiteral("/../VERSION");
QFile versionFile(verPath);
QString versVersion;
QString versDescription;
QString versDescLong;
// Open the file first to avoid a verify and then read issue where
// "auditors" might say its an issue,...
if (!versionFile.open(QIODevice::ReadOnly)) {
// No need to translate this should only be the case in development
// builds.
return;
} else {
// Expect a three line format of three HTML strings.
versVersion = QString::fromUtf8(versionFile.readLine()).trimmed();
versDescription = QString::fromUtf8(versionFile.readLine()).trimmed();
versDescLong = QString::fromUtf8(versionFile.readLine()).trimmed();
}
const QString sigPath = verPath + QStringLiteral(".sig");
QFileInfo versionSig(instPath + QStringLiteral("/../VERSION.sig"));
if (versionSig.exists()) {
/* We have a signed version so let us check it against the GnuPG
* release keys. */
QProcess gpgv;
gpgv.setProgram(Kleo::gpgPath().replace(QStringLiteral("gpg.exe"), QStringLiteral("gpgv.exe")));
const QString keyringPath(QStringLiteral("%1/../share/gnupg/distsigkey.gpg").arg(Kleo::gnupgInstallPath()));
gpgv.setArguments(QStringList() << QStringLiteral("--keyring") << keyringPath << QStringLiteral("--") << sigPath << verPath);
gpgv.start();
gpgv.waitForFinished();
if (gpgv.exitStatus() == QProcess::NormalExit && !gpgv.exitCode()) {
qCDebug(LIBKLEO_LOG) << "Valid Version: " << versVersion;
mVersion = versVersion;
mDescription = versDescription;
mDescLong = versDescLong;
mSignedVersion = true;
} else {
qCDebug(LIBKLEO_LOG) << "gpgv failed with stderr: " << gpgv.readAllStandardError();
qCDebug(LIBKLEO_LOG) << "gpgv stdout" << gpgv.readAllStandardOutput();
}
} else {
qCDebug(LIBKLEO_LOG) << "No signed VERSION file found.";
}
// Also take Version information from unsigned Versions.
mVersion = versVersion;
}
public:
const QString &version() const
{
return mVersion;
}
const QString &description() const
{
return mDescription;
}
const QString &longDescription() const
{
return mDescLong;
}
bool isSignedVersion() const
{
return mSignedVersion;
}
};
} // namespace
bool Kleo::gpg4winSignedversion()
{
return Gpg4win::instance()->isSignedVersion();
}
QString Kleo::gpg4winVersionNumber()
{
// extract the actual version number from the string returned by Gpg4win::version();
// we assume that Gpg4win::version() returns a version number (conforming to the semantic
// versioning spec) optionally prefixed with some text followed by a dash,
// e.g. "Gpg4win-3.1.15-beta15"; see https://dev.gnupg.org/T5663
static const QRegularExpression catchSemVerRegExp{QLatin1String{R"(-([0-9]+(?:\.[0-9]+)*(?:-[.0-9A-Za-z-]+)?(?:\+[.0-9a-zA-Z-]+)?)$)"}};
QString ret;
const auto match = catchSemVerRegExp.match(gpg4winVersion());
if (match.hasMatch()) {
ret = match.captured(1);
} else {
ret = gpg4winVersion();
}
qCDebug(LIBKLEO_LOG) << __func__ << "returns" << ret;
return ret;
}
QString Kleo::gpg4winVersion()
{
return Gpg4win::instance()->version();
}
QString Kleo::gpg4winDescription()
{
return Gpg4win::instance()->description();
}
QString Kleo::gpg4winLongDescription()
{
return Gpg4win::instance()->longDescription();
}
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<QByteArray> 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<int, 3> getVersionFromString(const char *actual, bool &ok)
{
std::array<int, 3> ret;
ok = false;
if (!actual) {
return ret;
}
QString versionString = QString::fromLatin1(actual);
// Try to fix it up
QRegExp rx(QLatin1String(R"((\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<Engine, std::array<int, 3>> cachedVersions;
const int required_version[] = {major, minor, patch};
// Gpgconf means spawning processes which is expensive on windows.
std::array<int, 3> 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;
}
return !Kleo::keyserver().isEmpty();
}
QString Kleo::keyserver()
{
QString result = getCryptoConfigStringValue("gpg", "keyserver");
if (result.isEmpty()) {
result = getCryptoConfigStringValue("dirmngr", "keyserver");
}
return result;
}
bool Kleo::haveX509DirectoryServerConfigured()
{
return !getCryptoConfigUrlList("dirmngr", "ldapserver").empty() //
|| !getCryptoConfigUrlList("dirmngr", "LDAP Server").empty() //
|| !getCryptoConfigUrlList("gpgsm", "keyserver").empty();
}
bool Kleo::gpgComplianceP(const char *mode)
{
const auto conf = QGpgME::cryptoConfig();
const auto entry = getCryptoConfigEntry(conf, "gpg", "compliance");
return entry && entry->stringValue() == QString::fromLatin1(mode);
}
bool Kleo::gnupgUsesDeVsCompliance()
{
return getCryptoConfigStringValue("gpg", "compliance") == QLatin1String{"de-vs"};
}
bool Kleo::gnupgIsDeVsCompliant()
{
if (!gnupgUsesDeVsCompliance()) {
return false;
}
// The pseudo option compliance_de_vs was fully added in 2.2.34;
// For versions between 2.2.28 and 2.2.33 there was a broken config
// value with a wrong type. So for them we add an extra check. This
// can be removed in future versions because. For GnuPG we could assume
// non-compliance for older versions as versions of Kleopatra for
// which this matters are bundled with new enough versions of GnuPG anyway
if (engineIsVersion(2, 2, 28) && !engineIsVersion(2, 2, 34)) {
return true;
}
return getCryptoConfigIntValue("gpg", "compliance_de_vs", 0) != 0;
}
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
}
QStringList Kleo::backendVersionInfo()
{
QStringList versions;
if (Kleo::engineIsVersion(2, 2, 24, GpgME::GpgConfEngine)) {
QProcess p;
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions ...";
p.start(Kleo::gpgConfPath(), {QStringLiteral("--show-versions")});
// wait at most 1 second
if (!p.waitForFinished(1000)) {
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions timed out after 1 second.";
} else if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) {
qCDebug(LIBKLEO_LOG) << "Running gpgconf --show-versions failed:" << p.errorString();
qCDebug(LIBKLEO_LOG) << "gpgconf stderr:" << p.readAllStandardError();
qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput();
} else {
const QByteArray output = p.readAllStandardOutput().replace("\r\n", "\n");
qCDebug(LIBKLEO_LOG) << "gpgconf stdout:" << p.readAllStandardOutput();
const auto lines = output.split('\n');
for (const auto &line : lines) {
if (line.startsWith("* GnuPG") || line.startsWith("* Libgcrypt")) {
const auto components = line.split(' ');
versions.push_back(QString::fromLatin1(components.at(1) + ' ' + components.value(2)));
}
}
}
}
return versions;
}
namespace
{
template<typename Function1, typename Function2>
auto startGpgConf(const QStringList &arguments, Function1 onSuccess, Function2 onFailure)
{
auto process = new QProcess;
process->setProgram(Kleo::gpgConfPath());
process->setArguments(arguments);
QObject::connect(process, &QProcess::started, [process]() {
qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") was started successfully";
});
QObject::connect(process, &QProcess::errorOccurred, [process, onFailure](auto error) {
qCDebug(LIBKLEO_LOG).nospace() << "Error while running gpgconf (" << process << "): " << error;
process->deleteLater();
onFailure();
});
QObject::connect(process, &QProcess::readyReadStandardError, [process]() {
for (const auto &line : process->readAllStandardError().trimmed().split('\n')) {
qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") stderr: " << line;
}
});
QObject::connect(process, &QProcess::readyReadStandardOutput, [process]() {
(void)process->readAllStandardOutput(); /* ignore stdout */
});
QObject::connect(process,
qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
[process, onSuccess, onFailure](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::NormalExit) {
qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") exited (exit code: " << exitCode << ")";
if (exitCode == 0) {
onSuccess();
} else {
onFailure();
}
} else {
qCDebug(LIBKLEO_LOG).nospace() << "gpgconf (" << process << ") crashed (exit code: " << exitCode << ")";
onFailure();
}
process->deleteLater();
});
qCDebug(LIBKLEO_LOG).nospace() << "Starting gpgconf (" << process << ") with arguments " << process->arguments().join(QLatin1Char(' ')) << " ...";
process->start();
return process;
}
static auto startGpgConf(const QStringList &arguments)
{
return startGpgConf(
arguments,
[]() {},
[]() {});
}
}
void Kleo::launchGpgAgent()
{
static QPointer<QProcess> process;
static qint64 mSecsSinceEpochOfLastLaunch = 0;
static int numberOfFailedLaunches = 0;
if (Kleo::Assuan::agentIsRunning()) {
qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already running";
return;
}
if (process) {
qCDebug(LIBKLEO_LOG) << __func__ << ": gpg-agent is already being launched";
return;
}
const auto now = QDateTime::currentMSecsSinceEpoch();
if (now - mSecsSinceEpochOfLastLaunch < 1000) {
// reduce attempts to launch the agent to 1 attempt per second
return;
}
mSecsSinceEpochOfLastLaunch = now;
if (numberOfFailedLaunches > 5) {
qCWarning(LIBKLEO_LOG) << __func__ << ": Launching gpg-agent failed" << numberOfFailedLaunches << "times in a row. Giving up.";
return;
}
process = startGpgConf(
{QStringLiteral("--launch"), QStringLiteral("gpg-agent")},
[]() {
numberOfFailedLaunches = 0;
},
[]() {
numberOfFailedLaunches++;
});
}
void Kleo::killDaemons()
{
static QPointer<QProcess> process;
if (process) {
qCDebug(LIBKLEO_LOG) << __func__ << ": The daemons are already being shut down";
return;
}
process = startGpgConf({QStringLiteral("--kill"), QStringLiteral("all")});
}
diff --git a/src/utils/scdaemon.cpp b/src/utils/scdaemon.cpp
index cc292552..b3640a02 100644
--- a/src/utils/scdaemon.cpp
+++ b/src/utils/scdaemon.cpp
@@ -1,52 +1,52 @@
/*
utils/scdaemon.cpp
This file is part of libkleopatra
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-libkleo.h>
#include "scdaemon.h"
#include "utils/assuan.h"
#include "utils/hex.h"
#include "utils/stringutils.h"
+#include <libkleo_debug.h>
+
#if __has_include(<QGpgME/Debug>)
#include <QGpgME/Debug>
#endif
#include <gpgme++/context.h>
-#include "libkleo_debug.h"
-
using namespace Kleo;
using namespace GpgME;
std::vector<std::string> Kleo::SCDaemon::getReaders(Error &err)
{
std::vector<std::string> result;
auto c = Context::createForEngine(AssuanEngine, &err);
if (err) {
qCDebug(LIBKLEO_LOG) << "Creating context for Assuan engine failed:" << err;
return result;
}
auto assuanContext = std::shared_ptr<Context>(c.release());
const std::string command = "SCD GETINFO reader_list";
const auto readers = Assuan::sendDataCommand(assuanContext, command.c_str(), err);
if (err) {
return result;
}
result = split(readers, '\n');
// remove empty entries; in particular, the last entry
result.erase(std::remove_if(std::begin(result), std::end(result), std::mem_fn(&std::string::empty)), std::end(result));
return result;
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Feb 26, 7:02 PM (1 d, 15 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
01/64/fe9e25aa83851e1de32218382f5d

Event Timeline