diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp
index 2c89ff918..c1fb45cc0 100644
--- a/src/commands/importcertificatescommand.cpp
+++ b/src/commands/importcertificatescommand.cpp
@@ -1,1090 +1,1089 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/importcertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "importcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "certifycertificatecommand.h"
#include "kleopatra_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include // for Qt::escape
#include
#include
#include
#include
#include
#include
#include
using namespace GpgME;
using namespace Kleo;
using namespace QGpgME;
static void disconnectConnection(const QMetaObject::Connection &connection)
{
// trivial function for disconnecting a signal-slot connection
QObject::disconnect(connection);
}
bool operator==(const ImportJobData &lhs, const ImportJobData &rhs)
{
return lhs.job == rhs.job;
}
namespace
{
make_comparator_str(ByImportFingerprint, .fingerprint());
class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
ImportResultProxyModel(const std::vector &results, QObject *parent = nullptr)
: AbstractKeyListSortFilterProxyModel(parent)
{
updateFindCache(results);
}
~ImportResultProxyModel() override
{
}
ImportResultProxyModel *clone() const override
{
// compiler-generated copy ctor is fine!
return new ImportResultProxyModel(*this);
}
void setImportResults(const std::vector &results)
{
updateFindCache(results);
invalidateFilter();
}
protected:
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid() || role != Qt::ToolTipRole) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
}
const QString fpr = index.data(KeyList::FingerprintRole).toString();
// find information:
const std::vector::const_iterator it =
Kleo::binary_find(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint());
if (it == m_importsByFingerprint.end()) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
} else {
QStringList rv;
const auto ids = m_idsByFingerprint[it->fingerprint()];
rv.reserve(ids.size());
std::copy(ids.cbegin(), ids.cend(), std::back_inserter(rv));
return Formatting::importMetaData(*it, rv);
}
}
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
//
// 0. Keep parents of matching children:
//
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
Q_ASSERT(index.isValid());
for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i)
if (filterAcceptsRow(i, index)) {
return true;
}
//
// 1. Check that this is an imported key:
//
const QString fpr = index.data(KeyList::FingerprintRole).toString();
return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint());
}
private:
void updateFindCache(const std::vector &results)
{
m_importsByFingerprint.clear();
m_idsByFingerprint.clear();
m_results = results;
for (const auto &r : results) {
const std::vector imports = r.result.imports();
m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end());
for (std::vector::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) {
m_idsByFingerprint[it->fingerprint()].insert(r.id);
}
}
std::sort(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), ByImportFingerprint());
}
private:
mutable std::vector m_importsByFingerprint;
mutable std::map, ByImportFingerprint> m_idsByFingerprint;
std::vector m_results;
};
bool importFailed(const ImportResultData &r)
{
// ignore GPG_ERR_EOF error to handle the "failed" import of files
// without X.509 certificates by gpgsm gracefully
return r.result.error() && r.result.error().code() != GPG_ERR_EOF;
}
bool importWasCanceled(const ImportResultData &r)
{
return r.result.error().isCanceled();
}
}
ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, progressWindowTitle{i18nc("@title:window", "Importing Certificates")}
, progressLabelText{i18n("Importing certificates... (this can take a while)")}
{
}
ImportCertificatesCommand::Private::~Private()
{
if (progressDialog) {
delete progressDialog;
}
}
#define d d_func()
#define q q_func()
ImportCertificatesCommand::ImportCertificatesCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ImportCertificatesCommand::ImportCertificatesCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ImportCertificatesCommand::~ImportCertificatesCommand() = default;
static QString format_ids(const std::vector &ids)
{
QStringList escapedIds;
for (const QString &id : ids) {
if (!id.isEmpty()) {
escapedIds << id.toHtmlEscaped();
}
}
return escapedIds.join(QLatin1StringView(" "));
}
static QString make_tooltip(const std::vector &results)
{
if (results.empty()) {
return {};
}
std::vector ids;
ids.reserve(results.size());
std::transform(std::begin(results), std::end(results), std::back_inserter(ids), [](const auto &r) {
return r.id;
});
std::sort(std::begin(ids), std::end(ids));
ids.erase(std::unique(std::begin(ids), std::end(ids)), std::end(ids));
if (ids.size() == 1)
if (ids.front().isEmpty()) {
return {};
} else
return i18nc("@info:tooltip", "Imported Certificates from %1", ids.front().toHtmlEscaped());
else
return i18nc("@info:tooltip", "Imported certificates from these sources: %1", format_ids(ids));
}
void ImportCertificatesCommand::Private::setImportResultProxyModel(const std::vector &results)
{
if (std::none_of(std::begin(results), std::end(results), [](const auto &r) {
return r.result.numConsidered() > 0;
})) {
return;
}
q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"), new ImportResultProxyModel(results), make_tooltip(results));
if (QTreeView *const tv = qobject_cast(parentWidgetOrView())) {
tv->expandAll();
}
}
int sum(const std::vector &res, int (ImportResult::*fun)() const)
{
return kdtools::accumulate_transform(res.begin(), res.end(), std::mem_fn(fun), 0);
}
static QString make_report(const std::vector &results, const std::vector &groups)
{
const KLocalizedString normalLine = ki18n("%1 %2 ");
const KLocalizedString boldLine = ki18n("%1 %2 ");
const KLocalizedString headerLine = ki18n("%1 ");
std::vector res;
res.reserve(results.size());
std::transform(std::begin(results), std::end(results), std::back_inserter(res), [](const auto &r) {
return r.result;
});
const auto numProcessedCertificates = sum(res, &ImportResult::numConsidered);
QStringList lines;
if (numProcessedCertificates > 0 || groups.size() == 0) {
lines.push_back(headerLine.subs(i18n("Certificates")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:")).subs(numProcessedCertificates).toString());
lines.push_back(normalLine.subs(i18n("Imported:")).subs(sum(res, &ImportResult::numImported)).toString());
if (const int n = sum(res, &ImportResult::newSignatures))
lines.push_back(normalLine.subs(i18n("New signatures:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::newUserIDs))
lines.push_back(normalLine.subs(i18n("New user IDs:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numKeysWithoutUserID))
lines.push_back(normalLine.subs(i18n("Certificates without user IDs:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::newSubkeys))
lines.push_back(normalLine.subs(i18n("New subkeys:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::newRevocations))
lines.push_back(boldLine.subs(i18n("Newly revoked:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::notImported))
lines.push_back(boldLine.subs(i18n("Not imported:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numUnchanged))
lines.push_back(normalLine.subs(i18n("Unchanged:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered))
lines.push_back(normalLine.subs(i18n("Secret keys processed:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysImported))
lines.push_back(normalLine.subs(i18n("Secret keys imported:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered) - sum(res, &ImportResult::numSecretKeysImported)
- sum(res, &ImportResult::numSecretKeysUnchanged))
if (n > 0)
lines.push_back(boldLine.subs(i18n("Secret keys not imported:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysUnchanged))
lines.push_back(normalLine.subs(i18n("Secret keys unchanged:")).subs(n).toString());
if (const int n = sum(res, &ImportResult::numV3KeysSkipped))
lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:")).subs(n).toString());
}
if (!lines.empty()) {
lines.push_back(headerLine.subs(QLatin1StringView{" "}).toString());
}
if (groups.size() > 0) {
const auto newGroups = std::count_if(std::begin(groups), std::end(groups), [](const auto &g) {
return g.status == ImportedGroup::Status::New;
});
const auto updatedGroups = groups.size() - newGroups;
lines.push_back(headerLine.subs(i18n("Certificate Groups")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:")).subs(groups.size()).toString());
lines.push_back(normalLine.subs(i18n("New groups:")).subs(newGroups).toString());
lines.push_back(normalLine.subs(i18n("Updated groups:")).subs(updatedGroups).toString());
}
return lines.join(QLatin1StringView{});
}
static bool isImportFromSingleSource(const std::vector &res)
{
return (res.size() == 1) || (res.size() == 2 && res[0].id == res[1].id);
}
static QString make_message_report(const std::vector &res, const std::vector &groups)
{
QString report{QLatin1StringView{""}};
if (res.empty()) {
report += i18n("No imports (should not happen, please report a bug).");
} else {
const QString title = isImportFromSingleSource(res) && !res.front().id.isEmpty() ? i18n("Detailed results of importing %1:", res.front().id)
: i18n("Detailed results of import:");
report += QLatin1StringView{""} + title + QLatin1String{"
"};
report += QLatin1StringView{"
"};
report += make_report(res, groups);
report += QLatin1StringView{"
"};
}
report += QLatin1StringView{""};
return report;
}
// Returns false on error, true if please certify was shown.
bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp)
{
if (!Kleo::userHasCertificationKey()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "No certification key available";
return false;
}
const char *fpr = imp.fingerprint();
if (!fpr) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Import without fingerprint";
return false;
}
// Exactly one public key imported. Let's see if it is openpgp. We are async here so
// we can just fetch it.
auto ctx = wrap_unique(GpgME::Context::createForProtocol(GpgME::OpenPGP));
if (!ctx) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto";
return false;
}
ctx->addKeyListMode(KeyListMode::WithSecret);
GpgME::Error err;
const auto key = ctx->key(fpr, err, false);
if (key.isNull() || err) {
// No such key most likely not OpenPGP
return false;
}
if (!Kleo::canBeCertified(key)) {
// key is expired or revoked
return false;
}
if (key.hasSecret()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "Secret key is available -> skipping certification";
return false;
}
for (const auto &uid : key.userIDs()) {
if (uid.validity() >= GpgME::UserID::Marginal) {
// Already marginal so don't bug the user
return false;
}
}
const QStringList suggestions = {
i18n("A phone call to the person."),
i18n("Using a business card."),
i18n("Confirming it on a trusted website."),
};
auto sel = KMessageBox::questionTwoActions(parentWidgetOrView(),
i18n("In order to mark the certificate as valid it needs to be certified.") + QStringLiteral(" ")
+ i18n("Certifying means that you check the Fingerprint.") + QStringLiteral(" ")
+ i18n("Some suggestions to do this are:")
+ QStringLiteral("%1").arg(suggestions.join(QStringLiteral(" ") + i18n("Do you wish to start this process now?"),
i18nc("@title", "You have imported a new certificate (public key)"),
KGuiItem(i18nc("@action:button", "Certify")),
KStandardGuiItem::cancel(),
QStringLiteral("CertifyQuestion"));
if (sel == KMessageBox::ButtonCode::PrimaryAction) {
QEventLoop loop;
auto cmd = new Commands::CertifyCertificateCommand(key);
cmd->setParentWidget(parentWidgetOrView());
connect(cmd, &Command::finished, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection);
loop.exec();
}
return true;
}
namespace
{
/**
* Returns the Import of an OpenPGP key, if a single certificate was imported and this was an OpenPGP key.
* Otherwise, returns a null Import.
*/
auto getSingleOpenPGPImport(const std::vector &res)
{
static const Import nullImport;
if (!isImportFromSingleSource(res)) {
return nullImport;
}
const auto numImported = std::accumulate(res.cbegin(), res.cend(), 0, [](auto s, const auto &r) {
return s + r.result.numImported();
});
if (numImported > 1) {
return nullImport;
}
if ((res.size() >= 1) && (res[0].protocol == GpgME::OpenPGP) && (res[0].result.numImported() == 1) && (res[0].result.imports().size() == 1)) {
return res[0].result.imports()[0];
} else if ((res.size() == 2) && (res[1].protocol == GpgME::OpenPGP) && (res[1].result.numImported() == 1) && (res[1].result.imports().size() == 1)) {
return res[1].result.imports()[0];
}
return nullImport;
}
auto consolidatedAuditLogEntries(const std::vector &res)
{
static const QString gpg = QStringLiteral("gpg");
static const QString gpgsm = QStringLiteral("gpgsm");
if (res.size() == 1) {
return res.front().auditLog;
}
QStringList auditLogs;
auto extractAndAnnotateAuditLog = [](const ImportResultData &r) {
QString s;
if (!r.id.isEmpty()) {
const auto program = r.protocol == GpgME::OpenPGP ? gpg : gpgsm;
const auto headerLine = i18nc("file name (imported with gpg/gpgsm)", "%1 (imported with %2)").arg(r.id, program);
s += QStringLiteral("%1
").arg(headerLine);
}
if (r.auditLog.error().code() == GPG_ERR_NO_DATA) {
s += QStringLiteral("") + i18nc("@info", "Audit log is empty.") + QStringLiteral(" ");
} else if (r.result.error().isCanceled()) {
s += QStringLiteral("") + i18nc("@info", "Import was canceled.") + QStringLiteral(" ");
} else {
s += r.auditLog.text();
}
return s;
};
std::transform(res.cbegin(), res.cend(), std::back_inserter(auditLogs), extractAndAnnotateAuditLog);
return AuditLogEntry{auditLogs.join(QLatin1StringView{" "}), Error{}};
}
}
void ImportCertificatesCommand::Private::showDetails(const std::vector &res, const std::vector &groups)
{
const auto singleOpenPGPImport = getSingleOpenPGPImport(res);
if (!singleOpenPGPImport.isNull()) {
if (showPleaseCertify(singleOpenPGPImport)) {
return;
}
}
setImportResultProxyModel(res);
MessageBox::information(parentWidgetOrView(), make_message_report(res, groups), consolidatedAuditLogEntries(res), i18n("Certificate Import Result"));
}
static QString make_error_message(const Error &err, const QString &id)
{
Q_ASSERT(err);
Q_ASSERT(!err.isCanceled());
if (id.isEmpty()) {
return i18n(
"An error occurred while trying to import the certificate:
"
"%1
",
Formatting::errorAsString(err));
} else {
return i18n(
"An error occurred while trying to import the certificate %1:
"
"%2
",
id,
Formatting::errorAsString(err));
}
}
void ImportCertificatesCommand::Private::showError(const ImportResultData &result)
{
MessageBox::error(parentWidgetOrView(), make_error_message(result.result.error(), result.id), result.auditLog);
}
void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait)
{
if (wait == waitForMoreJobs) {
return;
}
waitForMoreJobs = wait;
if (!waitForMoreJobs) {
tryToFinish();
}
}
void ImportCertificatesCommand::Private::onImportResult(const ImportResult &result, QGpgME::Job *finishedJob)
{
if (!finishedJob) {
finishedJob = qobject_cast(q->sender());
}
Q_ASSERT(finishedJob);
qCDebug(KLEOPATRA_LOG) << q << __func__ << finishedJob;
auto it = std::find_if(std::begin(runningJobs), std::end(runningJobs), [finishedJob](const auto &job) {
return job.job == finishedJob;
});
Q_ASSERT(it != std::end(runningJobs));
if (it == std::end(runningJobs)) {
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Finished job not found";
return;
}
Kleo::for_each(it->connections, &disconnectConnection);
it->connections.clear();
increaseProgressValue();
const auto job = *it;
addImportResult({job.id, job.protocol, job.type, result, AuditLogEntry::fromJob(finishedJob)}, job);
}
void ImportCertificatesCommand::Private::addImportResult(const ImportResultData &result, const ImportJobData &job)
{
qCDebug(KLEOPATRA_LOG) << q << __func__ << result.id << "Result:" << Formatting::errorAsString(result.result.error());
results.push_back(result);
if (importFailed(result)) {
showError(result);
}
if (job.job) {
const auto count = std::erase(runningJobs, job);
Q_ASSERT(count == 1);
}
tryToFinish();
}
static void handleOwnerTrust(const std::vector &results, QWidget *dialog)
{
std::unordered_set askedAboutFingerprints;
for (const auto &r : results) {
if (r.protocol != GpgME::Protocol::OpenPGP) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping non-OpenPGP import";
continue;
}
const auto imports = r.result.imports();
for (const auto &import : imports) {
if (!(import.status() & (Import::Status::NewKey | Import::Status::ContainedSecretKey))) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping already known imported public key";
continue;
}
const char *fpr = import.fingerprint();
if (!fpr) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import without fingerprint";
continue;
}
if (Kleo::contains(askedAboutFingerprints, fpr)) {
// imports of secret keys can result in multiple Imports for the same key
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import for already handled fingerprint";
continue;
}
GpgME::Error err;
auto ctx = wrap_unique(Context::createForProtocol(GpgME::Protocol::OpenPGP));
if (!ctx) {
qCWarning(KLEOPATRA_LOG) << "Failed to get context";
continue;
}
ctx->addKeyListMode(KeyListMode::WithSecret);
const Key toTrustOwner = ctx->key(fpr, err, false);
if (toTrustOwner.isNull() || !toTrustOwner.hasSecret()) {
continue;
}
if (toTrustOwner.ownerTrust() == Key::OwnerTrust::Ultimate) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping key with ultimate ownertrust";
continue;
}
const auto toTrustOwnerUserIDs{toTrustOwner.userIDs()};
// ki18n(" ") as initializer because initializing with empty string leads to
// (I18N_EMPTY_MESSAGE)
const KLocalizedString uids = std::accumulate(toTrustOwnerUserIDs.cbegin(),
toTrustOwnerUserIDs.cend(),
KLocalizedString{ki18n(" ")},
[](KLocalizedString temp, const auto &uid) {
return kxi18nc("@info", "%1- %2
").subs(temp).subs(Formatting::prettyNameAndEMail(uid));
});
const QString str = xi18nc("@info",
"You have imported a certificate with fingerprint "
"%1 "
""
"and user IDs"
"%2
"
" "
"Is this your own certificate? ",
Formatting::prettyID(fpr),
uids);
int k = KMessageBox::questionTwoActionsCancel(dialog,
str,
i18nc("@title:window", "Mark Own Certificate"),
KGuiItem{i18nc("@action:button", "Yes, It's Mine")},
KGuiItem{i18nc("@action:button", "No, It's Not Mine")});
askedAboutFingerprints.insert(fpr);
if (k == KMessageBox::ButtonCode::PrimaryAction) {
// To use the ChangeOwnerTrustJob over
// the CryptoBackendFactory
const QGpgME::Protocol *const backend = QGpgME::openpgp();
if (!backend) {
qCWarning(KLEOPATRA_LOG) << "Failed to get CryptoBackend";
return;
}
ChangeOwnerTrustJob *const j = backend->changeOwnerTrustJob();
j->start(toTrustOwner, Key::Ultimate);
} else if (k == KMessageBox::ButtonCode::Cancel) {
// do not bother the user with further "Is this yours?" questions
return;
}
}
}
}
static void validateImportedCertificate(const GpgME::Import &import)
{
if (const auto fpr = import.fingerprint()) {
auto key = KeyCache::instance()->findByFingerprint(fpr);
if (!key.isNull()) {
// this triggers a keylisting with validation for this certificate
key.update();
} else {
qCWarning(KLEOPATRA_LOG) << __func__ << "Certificate with fingerprint" << fpr << "not found";
}
}
}
static void handleExternalCMSImports(const std::vector &results)
{
// For external CMS Imports we have to manually do a keylist
// with validation to get the intermediate and root ca imported
// automatically if trusted-certs and extra-certs are used.
for (const auto &r : results) {
if (r.protocol == GpgME::CMS && r.type == ImportType::External && !importFailed(r) && !importWasCanceled(r)) {
const auto imports = r.result.imports();
std::for_each(std::begin(imports), std::end(imports), &validateImportedCertificate);
}
}
}
void ImportCertificatesCommand::Private::processResults()
{
importGroups();
if (Settings{}.retrieveSignerKeysAfterImport() && !importingSignerKeys) {
importingSignerKeys = true;
const auto missingSignerKeys = getMissingSignerKeyIds(results);
if (!missingSignerKeys.empty()) {
importSignerKeys(missingSignerKeys);
return;
}
}
handleExternalCMSImports(results);
// ensure that the progress dialog is closed before we show any other dialogs
setProgressToMaximum();
handleOwnerTrust(results, parentWidgetOrView());
showDetails(results, importedGroups);
auto tv = dynamic_cast(view());
if (!tv) {
qCDebug(KLEOPATRA_LOG) << "Failed to find treeview";
} else {
tv->expandAll();
}
finished();
}
void ImportCertificatesCommand::Private::tryToFinish()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
if (waitForMoreJobs) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "Waiting for more jobs -> keep going";
return;
}
if (!runningJobs.empty()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are unfinished jobs -> keep going";
return;
}
if (!pendingJobs.empty()) {
qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are pending jobs -> start the next one";
auto job = pendingJobs.front();
pendingJobs.pop();
job.job->startNow();
runningJobs.push_back(job);
return;
}
if (keyListConnection) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "There is already a valid keyListConnection!";
} else {
auto keyCache = KeyCache::mutableInstance();
keyListConnection = connect(keyCache.get(), &KeyCache::keyListingDone, q, [this]() {
keyCacheUpdated();
});
keyCache->startKeyListing();
}
}
void ImportCertificatesCommand::Private::keyCacheUpdated()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
if (!disconnect(keyListConnection)) {
qCWarning(KLEOPATRA_LOG) << q << __func__ << "Failed to disconnect keyListConnection";
}
keyCacheAutoRefreshSuspension.reset();
const auto allIds = std::accumulate(std::cbegin(results), std::cend(results), std::set{}, [](auto allIds, const auto &r) {
allIds.insert(r.id);
return allIds;
});
const auto canceledIds = std::accumulate(std::cbegin(results), std::cend(results), std::set{}, [](auto canceledIds, const auto &r) {
if (importWasCanceled(r)) {
canceledIds.insert(r.id);
}
return canceledIds;
});
const auto totalConsidered = std::accumulate(std::cbegin(results), std::cend(results), 0, [](auto totalConsidered, const auto &r) {
return totalConsidered + r.result.numConsidered();
});
if (totalConsidered == 0 && canceledIds.size() == allIds.size()) {
// nothing was considered for import and at least one import per id was
// canceled => treat the command as canceled
canceled();
return;
}
processResults();
}
static ImportedGroup storeGroup(const KeyGroup &group, const QString &id, QWidget *parent)
{
if (Kleo::any_of(group.keys(), [](const auto &key) {
return !key.hasEncrypt();
})) {
KMessageBox::information(parent,
xi18nc("@info",
"The imported group %1 contains certificates that cannot be used for encryption. "
"This may lead to unexpected results. ",
group.name()));
}
const auto status = KeyCache::instance()->group(group.id()).isNull() ? ImportedGroup::Status::New : ImportedGroup::Status::Updated;
if (status == ImportedGroup::Status::New) {
KeyCache::mutableInstance()->insert(group);
} else {
KeyCache::mutableInstance()->update(group);
}
return {id, group, status};
}
void ImportCertificatesCommand::Private::importGroups()
{
for (const auto &path : filesToImportGroupsFrom) {
const bool certificateImportSucceeded = std::any_of(std::cbegin(results), std::cend(results), [path](const auto &r) {
return r.id == path && !importFailed(r) && !importWasCanceled(r);
});
if (certificateImportSucceeded) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Importing groups from file" << path;
const auto groups = readKeyGroups(path);
std::transform(std::begin(groups), std::end(groups), std::back_inserter(importedGroups), [path, this](const auto &group) {
return storeGroup(group, path, parentWidgetOrView());
});
}
increaseProgressValue();
}
filesToImportGroupsFrom.clear();
}
static auto accumulateNewKeys(std::vector &fingerprints, const std::vector &imports)
{
return std::accumulate(std::begin(imports), std::end(imports), fingerprints, [](auto fingerprints, const auto &import) {
if (import.status() == Import::NewKey) {
fingerprints.push_back(import.fingerprint());
}
return fingerprints;
});
}
static auto accumulateNewOpenPGPKeys(const std::vector &results)
{
return std::accumulate(std::begin(results), std::end(results), std::vector{}, [](auto fingerprints, const auto &r) {
if (r.protocol == GpgME::OpenPGP) {
fingerprints = accumulateNewKeys(fingerprints, r.result.imports());
}
return fingerprints;
});
}
std::set ImportCertificatesCommand::Private::getMissingSignerKeyIds(const std::vector &results)
{
auto newOpenPGPKeys = KeyCache::instance()->findByFingerprint(accumulateNewOpenPGPKeys(results));
// update all new OpenPGP keys to get information about certifications
std::for_each(std::begin(newOpenPGPKeys), std::end(newOpenPGPKeys), std::mem_fn(&Key::update));
auto missingSignerKeyIds = Kleo::getMissingSignerKeyIds(newOpenPGPKeys);
return missingSignerKeyIds;
}
void ImportCertificatesCommand::Private::importSignerKeys(const std::set &keyIds)
{
Q_ASSERT(!keyIds.empty());
setProgressLabelText(i18np("Fetching 1 signer key... (this can take a while)", "Fetching %1 signer keys... (this can take a while)", keyIds.size()));
setWaitForMoreJobs(true);
// start one import per key id to allow canceling the key retrieval without
// losing already retrieved keys
for (const auto &keyId : keyIds) {
startImport(GpgME::OpenPGP, {keyId}, QStringLiteral("Retrieve Signer Keys"));
}
setWaitForMoreJobs(false);
}
static std::unique_ptr get_import_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr(backend->importJob());
} else {
return std::unique_ptr();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol,
const QByteArray &data,
const QString &id,
[[maybe_unused]] const ImportOptions &options)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr job = get_import_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.", Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
addImportResult({id, protocol, ImportType::Local, ImportResult{}, AuditLogEntry{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(),
&AbstractImportJob::result,
q,
[this](const GpgME::ImportResult &result) {
onImportResult(result);
}),
connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress),
};
job->setImportFilter(options.importFilter);
job->setKeyOrigin(options.keyOrigin, options.keyOriginUrl);
const GpgME::Error err = job->startLater(data);
if (err.code()) {
addImportResult({id, protocol, ImportType::Local, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
pendingJobs.push({id, protocol, ImportType::Local, job.release(), connections});
}
}
static std::unique_ptr get_import_from_keyserver_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr(backend->importFromKeyserverJob());
} else {
return std::unique_ptr();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const std::vector &keys, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr job = get_import_from_keyserver_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.", Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(),
&AbstractImportJob::result,
q,
[this](const GpgME::ImportResult &result) {
onImportResult(result);
}),
connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress),
};
const GpgME::Error err = job->start(keys);
if (err.code()) {
addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
}
static auto get_receive_keys_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
std::unique_ptr job{};
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
job.reset(backend->receiveKeysJob());
}
return job;
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, [[maybe_unused]] const QStringList &keyIds, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
auto job = get_receive_keys_job(protocol);
if (!job.get()) {
qCWarning(KLEOPATRA_LOG) << "Failed to get ReceiveKeysJob for protocol" << Formatting::displayName(protocol);
addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(),
&AbstractImportJob::result,
q,
[this](const GpgME::ImportResult &result) {
onImportResult(result);
}),
connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress),
};
const GpgME::Error err = job->start(keyIds);
if (err.code()) {
addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}});
} else {
increaseProgressMaximum();
runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
}
void ImportCertificatesCommand::Private::importGroupsFromFile(const QString &filename)
{
increaseProgressMaximum();
filesToImportGroupsFrom.push_back(filename);
}
void ImportCertificatesCommand::Private::setUpProgressDialog()
{
if (progressDialog) {
return;
}
progressDialog = new QProgressDialog{parentWidgetOrView()};
// use a non-modal progress dialog to avoid reentrancy problems (and crashes) if multiple jobs finish in the same event loop cycle
// (cf. the warning for QProgressDialog::setValue() in the API documentation)
progressDialog->setModal(false);
progressDialog->setWindowTitle(progressWindowTitle);
progressDialog->setLabelText(progressLabelText);
progressDialog->setMinimumDuration(1000);
progressDialog->setMaximum(1);
progressDialog->setValue(0);
connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel);
connect(q, &Command::finished, progressDialog, [this]() {
progressDialog->accept();
});
}
void ImportCertificatesCommand::Private::setProgressWindowTitle(const QString &title)
{
if (progressDialog) {
progressDialog->setWindowTitle(title);
} else {
progressWindowTitle = title;
}
}
void ImportCertificatesCommand::Private::setProgressLabelText(const QString &text)
{
if (progressDialog) {
progressDialog->setLabelText(text);
} else {
progressLabelText = text;
}
}
void ImportCertificatesCommand::Private::increaseProgressMaximum()
{
setUpProgressDialog();
progressDialog->setMaximum(progressDialog->maximum() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::increaseProgressValue()
{
progressDialog->setValue(progressDialog->value() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::setProgressToMaximum()
{
qCDebug(KLEOPATRA_LOG) << __func__;
progressDialog->setValue(progressDialog->maximum());
}
void ImportCertificatesCommand::doCancel()
{
const auto jobsToCancel = d->runningJobs;
std::for_each(std::begin(jobsToCancel), std::end(jobsToCancel), [this](const auto &job) {
if (!job.connections.empty()) {
// ignore jobs without connections; they are already completed
qCDebug(KLEOPATRA_LOG) << "Canceling job" << job.job;
job.job->slotCancel();
d->onImportResult(ImportResult{Error::fromCode(GPG_ERR_CANCELED)}, job.job);
}
});
}
#undef d
#undef q
#include "importcertificatescommand.moc"
#include "moc_importcertificatescommand.cpp"
diff --git a/src/crypto/encryptemailtask.cpp b/src/crypto/encryptemailtask.cpp
index dda1e3224..473579a28 100644
--- a/src/crypto/encryptemailtask.cpp
+++ b/src/crypto/encryptemailtask.cpp
@@ -1,239 +1,238 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/encryptemailtask.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
#include "encryptemailtask.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include // for Qt::escape
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
namespace
{
class EncryptEMailResult : public Task::Result
{
const EncryptionResult m_result;
const AuditLogEntry m_auditLog;
public:
EncryptEMailResult(const EncryptionResult &r, const AuditLogEntry &auditLog)
: Task::Result()
, m_result(r)
, m_auditLog(auditLog)
{
}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override;
QString errorString() const override;
VisualCode code() const override;
AuditLogEntry auditLog() const override;
};
QString makeResultString(const EncryptionResult &res)
{
const Error err = res.error();
if (err.isCanceled()) {
return i18n("Encryption canceled.");
}
if (err) {
return i18n("Encryption failed: %1", Formatting::errorAsString(err).toHtmlEscaped());
}
return i18n("Encryption succeeded.");
}
}
class EncryptEMailTask::Private
{
friend class ::Kleo::Crypto::EncryptEMailTask;
EncryptEMailTask *const q;
public:
explicit Private(EncryptEMailTask *qq);
private:
std::unique_ptr createJob(GpgME::Protocol proto);
private:
void slotResult(const EncryptionResult &);
private:
std::shared_ptr input;
std::shared_ptr output;
std::vector recipients;
QPointer job;
};
EncryptEMailTask::Private::Private(EncryptEMailTask *qq)
: q(qq)
, input()
, output()
, job(nullptr)
{
}
EncryptEMailTask::EncryptEMailTask(QObject *p)
: Task(p)
, d(new Private(this))
{
}
EncryptEMailTask::~EncryptEMailTask()
{
}
void EncryptEMailTask::setInput(const std::shared_ptr &input)
{
kleo_assert(!d->job);
kleo_assert(input);
d->input = input;
}
void EncryptEMailTask::setOutput(const std::shared_ptr &output)
{
kleo_assert(!d->job);
kleo_assert(output);
d->output = output;
}
void EncryptEMailTask::setRecipients(const std::vector &recipients)
{
kleo_assert(!d->job);
kleo_assert(!recipients.empty());
d->recipients = recipients;
}
Protocol EncryptEMailTask::protocol() const
{
kleo_assert(!d->recipients.empty());
return d->recipients.front().protocol();
}
QString EncryptEMailTask::label() const
{
return d->input ? d->input->label() : QString();
}
unsigned long long EncryptEMailTask::inputSize() const
{
return d->input ? d->input->size() : 0;
}
void EncryptEMailTask::doStart()
{
kleo_assert(!d->job);
kleo_assert(d->input);
kleo_assert(d->output);
kleo_assert(!d->recipients.empty());
std::unique_ptr job = d->createJob(protocol());
kleo_assert(job.get());
job->start(d->recipients,
d->input->ioDevice(),
d->output->ioDevice(),
/*alwaysTrust=*/true);
d->job = job.release();
}
void EncryptEMailTask::cancel()
{
if (d->job) {
d->job->slotCancel();
}
}
std::unique_ptr EncryptEMailTask::Private::createJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
bool shouldArmor = (proto == OpenPGP || q->asciiArmor()) && !output->binaryOpt();
std::unique_ptr encryptJob(backend->encryptJob(shouldArmor, /*textmode=*/false));
kleo_assert(encryptJob.get());
if (proto == CMS && !q->asciiArmor() && !output->binaryOpt()) {
encryptJob->setOutputIsBase64Encoded(true);
}
connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &EncryptEMailTask::setProgress);
connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult, QByteArray)), q, SLOT(slotResult(GpgME::EncryptionResult)));
return encryptJob;
}
void EncryptEMailTask::Private::slotResult(const EncryptionResult &result)
{
const auto *const job = qobject_cast(q->sender());
if (result.error().code()) {
output->cancel();
} else {
output->finalize();
}
q->emitResult(std::shared_ptr(new EncryptEMailResult(result, AuditLogEntry::fromJob(job))));
}
QString EncryptEMailResult::overview() const
{
return makeOverview(makeResultString(m_result));
}
QString EncryptEMailResult::details() const
{
return QString();
}
GpgME::Error EncryptEMailResult::error() const
{
return m_result.error();
}
QString EncryptEMailResult::errorString() const
{
return hasError() ? makeResultString(m_result) : QString();
}
AuditLogEntry EncryptEMailResult::auditLog() const
{
return m_auditLog;
}
Task::Result::VisualCode EncryptEMailResult::code() const
{
if (m_result.error().isCanceled()) {
return Warning;
}
return m_result.error().code() ? NeutralError : NeutralSuccess;
}
#include "moc_encryptemailtask.cpp"
diff --git a/src/crypto/signemailtask.cpp b/src/crypto/signemailtask.cpp
index 9d3aa0e1a..7f6f7e170 100644
--- a/src/crypto/signemailtask.cpp
+++ b/src/crypto/signemailtask.cpp
@@ -1,290 +1,289 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/signemailtask.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
#include "signemailtask.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include // for Qt::escape
#include
#include
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
namespace
{
class SignEMailResult : public Task::Result
{
const SigningResult m_result;
const AuditLogEntry m_auditLog;
public:
explicit SignEMailResult(const SigningResult &r, const AuditLogEntry &auditLog)
: Task::Result()
, m_result(r)
, m_auditLog(auditLog)
{
}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override;
QString errorString() const override;
VisualCode code() const override;
AuditLogEntry auditLog() const override;
};
QString makeResultString(const SigningResult &res)
{
const Error err = res.error();
if (err.isCanceled()) {
return i18n("Signing canceled.");
}
if (err) {
return i18n("Signing failed: %1", Formatting::errorAsString(err).toHtmlEscaped());
}
return i18n("Signing succeeded.");
}
}
class SignEMailTask::Private
{
friend class ::Kleo::Crypto::SignEMailTask;
SignEMailTask *const q;
public:
explicit Private(SignEMailTask *qq);
private:
std::unique_ptr createJob(GpgME::Protocol proto);
private:
void slotResult(const SigningResult &);
private:
std::shared_ptr input;
std::shared_ptr output;
std::vector signers;
bool detached;
bool clearsign;
QString micAlg;
QPointer job;
};
SignEMailTask::Private::Private(SignEMailTask *qq)
: q(qq)
, input()
, output()
, signers()
, detached(false)
, clearsign(false)
, micAlg()
, job(nullptr)
{
}
SignEMailTask::SignEMailTask(QObject *p)
: Task(p)
, d(new Private(this))
{
}
SignEMailTask::~SignEMailTask()
{
}
void SignEMailTask::setInput(const std::shared_ptr &input)
{
kleo_assert(!d->job);
kleo_assert(input);
d->input = input;
}
void SignEMailTask::setOutput(const std::shared_ptr &output)
{
kleo_assert(!d->job);
kleo_assert(output);
d->output = output;
}
void SignEMailTask::setSigners(const std::vector &signers)
{
kleo_assert(!d->job);
kleo_assert(!signers.empty());
kleo_assert(std::none_of(signers.cbegin(), signers.cend(), std::mem_fn(&Key::isNull)));
d->signers = signers;
}
void SignEMailTask::setDetachedSignature(bool detached)
{
kleo_assert(!d->job);
d->detached = detached;
d->clearsign = false;
}
void SignEMailTask::setClearsign(bool clear)
{
kleo_assert(!d->job);
d->clearsign = clear;
d->detached = false;
}
Protocol SignEMailTask::protocol() const
{
kleo_assert(!d->signers.empty());
return d->signers.front().protocol();
}
QString SignEMailTask::label() const
{
return d->input ? d->input->label() : QString();
}
unsigned long long SignEMailTask::inputSize() const
{
return d->input ? d->input->size() : 0;
}
void SignEMailTask::doStart()
{
kleo_assert(!d->job);
kleo_assert(d->input);
kleo_assert(d->output);
kleo_assert(!d->signers.empty());
d->micAlg.clear();
std::unique_ptr job = d->createJob(protocol());
kleo_assert(job.get());
job->start(d->signers,
d->input->ioDevice(),
d->output->ioDevice(),
d->clearsign ? GpgME::Clearsigned
: d->detached ? GpgME::Detached
: GpgME::NormalSignatureMode);
d->job = job.release();
}
void SignEMailTask::cancel()
{
if (d->job) {
d->job->slotCancel();
}
}
std::unique_ptr SignEMailTask::Private::createJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
bool shouldArmor = (proto == OpenPGP || q->asciiArmor()) && !output->binaryOpt();
std::unique_ptr signJob(backend->signJob(/*armor=*/shouldArmor, /*textmode=*/false));
kleo_assert(signJob.get());
if (proto == CMS && !q->asciiArmor() && !output->binaryOpt()) {
signJob->setOutputIsBase64Encoded(true);
}
connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEMailTask::setProgress);
connect(signJob.get(), SIGNAL(result(GpgME::SigningResult, QByteArray)), q, SLOT(slotResult(GpgME::SigningResult)));
return signJob;
}
static QString collect_micalgs(const GpgME::SigningResult &result, GpgME::Protocol proto)
{
const std::vector css = result.createdSignatures();
QStringList micalgs;
std::transform(css.begin(), css.end(), std::back_inserter(micalgs), [](const GpgME::CreatedSignature &sig) {
return QString::fromLatin1(sig.hashAlgorithmAsString()).toLower();
});
if (proto == GpgME::OpenPGP)
for (QStringList::iterator it = micalgs.begin(), end = micalgs.end(); it != end; ++it) {
it->prepend(QLatin1StringView("pgp-"));
}
micalgs.sort();
micalgs.erase(std::unique(micalgs.begin(), micalgs.end()), micalgs.end());
return micalgs.join(QLatin1Char(','));
}
void SignEMailTask::Private::slotResult(const SigningResult &result)
{
const auto *const job = qobject_cast(q->sender());
if (result.error().code()) {
output->cancel();
} else {
output->finalize();
micAlg = collect_micalgs(result, q->protocol());
}
q->emitResult(std::shared_ptr(new SignEMailResult(result, AuditLogEntry::fromJob(job))));
}
QString SignEMailTask::micAlg() const
{
return d->micAlg;
}
QString SignEMailResult::overview() const
{
return makeOverview(makeResultString(m_result));
}
QString SignEMailResult::details() const
{
return QString();
}
GpgME::Error SignEMailResult::error() const
{
return m_result.error();
}
QString SignEMailResult::errorString() const
{
return hasError() ? makeResultString(m_result) : QString();
}
Task::Result::VisualCode SignEMailResult::code() const
{
if (m_result.error().isCanceled()) {
return Warning;
}
return m_result.error().code() ? NeutralError : NeutralSuccess;
}
AuditLogEntry SignEMailResult::auditLog() const
{
return m_auditLog;
}
#include "moc_signemailtask.cpp"
diff --git a/src/crypto/signencrypttask.cpp b/src/crypto/signencrypttask.cpp
index 8e9da4278..2b4aa33d3 100644
--- a/src/crypto/signencrypttask.cpp
+++ b/src/crypto/signencrypttask.cpp
@@ -1,973 +1,972 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/signencrypttask.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
#include "signencrypttask.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kleopatra_debug.h"
#include
#include
-#include // for Qt::escape
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
namespace
{
QString formatInputOutputLabel(const QString &input, const QString &output, bool outputDeleted)
{
return i18nc("Input file --> Output file (rarr is arrow",
"%1 → %2",
input.toHtmlEscaped(),
outputDeleted ? QStringLiteral("%1 ").arg(output.toHtmlEscaped()) : output.toHtmlEscaped());
}
class ErrorResult : public Task::Result
{
public:
ErrorResult(bool sign, bool encrypt, const Error &err, const QString &errStr, const QString &input, const QString &output, const AuditLogEntry &auditLog)
: Task::Result()
, m_sign(sign)
, m_encrypt(encrypt)
, m_error(err)
, m_errString(errStr)
, m_inputLabel(input)
, m_outputLabel(output)
, m_auditLog(auditLog)
{
}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override
{
return m_error;
}
QString errorString() const override
{
return m_errString;
}
VisualCode code() const override
{
return NeutralError;
}
AuditLogEntry auditLog() const override
{
return m_auditLog;
}
private:
const bool m_sign;
const bool m_encrypt;
const Error m_error;
const QString m_errString;
const QString m_inputLabel;
const QString m_outputLabel;
const AuditLogEntry m_auditLog;
};
namespace
{
struct LabelAndError {
QString label;
QString errorString;
};
}
class SignEncryptFilesResult : public Task::Result
{
public:
SignEncryptFilesResult(const SigningResult &sr, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog)
: Task::Result()
, m_sresult(sr)
, m_input{input}
, m_output{output}
, m_outputCreated(outputCreated)
, m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_sresult.isNull());
}
SignEncryptFilesResult(const EncryptionResult &er,
const LabelAndError &input,
const LabelAndError &output,
bool outputCreated,
const AuditLogEntry &auditLog)
: Task::Result()
, m_eresult(er)
, m_input{input}
, m_output{output}
, m_outputCreated(outputCreated)
, m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_eresult.isNull());
}
SignEncryptFilesResult(const SigningResult &sr,
const EncryptionResult &er,
const LabelAndError &input,
const LabelAndError &output,
bool outputCreated,
const AuditLogEntry &auditLog)
: Task::Result()
, m_sresult(sr)
, m_eresult(er)
, m_input{input}
, m_output{output}
, m_outputCreated(outputCreated)
, m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_sresult.isNull() || !m_eresult.isNull());
}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override;
QString errorString() const override;
VisualCode code() const override;
AuditLogEntry auditLog() const override;
private:
const SigningResult m_sresult;
const EncryptionResult m_eresult;
const LabelAndError m_input;
const LabelAndError m_output;
const bool m_outputCreated;
const AuditLogEntry m_auditLog;
};
static QString makeSigningOverview(const Error &err)
{
if (err.isCanceled()) {
return i18n("Signing canceled.");
}
if (err) {
return i18n("Signing failed.");
}
return i18n("Signing succeeded.");
}
static QString makeResultOverview(const SigningResult &result)
{
return makeSigningOverview(result.error());
}
static QString makeEncryptionOverview(const Error &err)
{
if (err.isCanceled()) {
return i18n("Encryption canceled.");
}
if (err) {
return i18n("Encryption failed.");
}
return i18n("Encryption succeeded.");
}
static QString makeResultOverview(const EncryptionResult &result)
{
return makeEncryptionOverview(result.error());
}
static QString makeResultOverview(const SigningResult &sr, const EncryptionResult &er)
{
if (er.isNull() && sr.isNull()) {
return QString();
}
if (er.isNull()) {
return makeResultOverview(sr);
}
if (sr.isNull()) {
return makeResultOverview(er);
}
if (sr.error().isCanceled() || sr.error()) {
return makeResultOverview(sr);
}
if (er.error().isCanceled() || er.error()) {
return makeResultOverview(er);
}
return i18n("Signing and encryption succeeded.");
}
static QString escape(QString s)
{
s = s.toHtmlEscaped();
s.replace(QLatin1Char('\n'), QStringLiteral(" "));
return s;
}
static QString makeResultDetails(const SigningResult &result, const QString &inputError, const QString &outputError)
{
const Error err = result.error();
if (err.code() == GPG_ERR_EIO) {
if (!inputError.isEmpty()) {
return i18n("Input error: %1", escape(inputError));
} else if (!outputError.isEmpty()) {
return i18n("Output error: %1", escape(outputError));
}
}
if (err || err.isCanceled()) {
return Formatting::errorAsString(err).toHtmlEscaped();
}
return QString();
}
static QString makeResultDetails(const EncryptionResult &result, const QString &inputError, const QString &outputError)
{
const Error err = result.error();
if (err.code() == GPG_ERR_EIO) {
if (!inputError.isEmpty()) {
return i18n("Input error: %1", escape(inputError));
} else if (!outputError.isEmpty()) {
return i18n("Output error: %1", escape(outputError));
}
}
if (err || err.isCanceled()) {
return Formatting::errorAsString(err).toHtmlEscaped();
}
return i18n(" Encryption succeeded.");
}
}
QString ErrorResult::overview() const
{
Q_ASSERT(m_error || m_error.isCanceled());
Q_ASSERT(m_sign || m_encrypt);
const QString label = formatInputOutputLabel(m_inputLabel, m_outputLabel, true);
const bool canceled = m_error.isCanceled();
if (m_sign && m_encrypt) {
return canceled ? i18n("%1: Sign/encrypt canceled. ", label) : i18n(" %1: Sign/encrypt failed.", label);
}
return i18nc("label: result. Example: foo -> foo.gpg: Encryption failed.",
"%1: %2 ",
label,
m_sign ? makeSigningOverview(m_error) : makeEncryptionOverview(m_error));
}
QString ErrorResult::details() const
{
return m_errString;
}
class SignEncryptTask::Private
{
friend class ::Kleo::Crypto::SignEncryptTask;
SignEncryptTask *const q;
public:
explicit Private(SignEncryptTask *qq);
private:
QString inputLabel() const;
QString outputLabel() const;
bool removeExistingOutputFile();
void startSignEncryptJob(GpgME::Protocol proto);
std::unique_ptr createSignJob(GpgME::Protocol proto);
std::unique_ptr createSignEncryptJob(GpgME::Protocol proto);
std::unique_ptr createEncryptJob(GpgME::Protocol proto);
void startSignEncryptArchiveJob(GpgME::Protocol proto);
std::unique_ptr createSignArchiveJob(GpgME::Protocol proto);
std::unique_ptr createSignEncryptArchiveJob(GpgME::Protocol proto);
std::unique_ptr createEncryptArchiveJob(GpgME::Protocol proto);
std::shared_ptr makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog);
private:
void slotResult(const SigningResult &);
void slotResult(const SigningResult &, const EncryptionResult &);
void slotResult(const EncryptionResult &);
void slotResult(const QGpgME::Job *, const SigningResult &, const EncryptionResult &);
private:
std::shared_ptr input;
std::shared_ptr output;
QStringList inputFileNames;
QString outputFileName;
std::vector signers;
std::vector recipients;
bool sign : 1;
bool encrypt : 1;
bool detached : 1;
bool symmetric : 1;
bool clearsign : 1;
bool archive : 1;
QPointer job;
QString labelText;
std::shared_ptr m_overwritePolicy;
};
SignEncryptTask::Private::Private(SignEncryptTask *qq)
: q{qq}
, sign{true}
, encrypt{true}
, detached{false}
, clearsign{false}
, archive{false}
, m_overwritePolicy{new OverwritePolicy{OverwritePolicy::Ask}}
{
q->setAsciiArmor(true);
}
std::shared_ptr SignEncryptTask::Private::makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog)
{
return std::shared_ptr(new ErrorResult(sign, encrypt, err, errStr, inputLabel(), outputLabel(), auditLog));
}
SignEncryptTask::SignEncryptTask(QObject *p)
: Task(p)
, d(new Private(this))
{
}
SignEncryptTask::~SignEncryptTask()
{
}
void SignEncryptTask::setInputFileName(const QString &fileName)
{
kleo_assert(!d->job);
kleo_assert(!fileName.isEmpty());
d->inputFileNames = QStringList(fileName);
}
void SignEncryptTask::setInputFileNames(const QStringList &fileNames)
{
kleo_assert(!d->job);
kleo_assert(!fileNames.empty());
d->inputFileNames = fileNames;
}
void SignEncryptTask::setInput(const std::shared_ptr &input)
{
kleo_assert(!d->job);
kleo_assert(input);
d->input = input;
}
void SignEncryptTask::setOutput(const std::shared_ptr &output)
{
kleo_assert(!d->job);
kleo_assert(output);
d->output = output;
}
void SignEncryptTask::setOutputFileName(const QString &fileName)
{
kleo_assert(!d->job);
kleo_assert(!fileName.isEmpty());
d->outputFileName = fileName;
}
QString SignEncryptTask::outputFileName() const
{
return d->outputFileName;
}
void SignEncryptTask::setSigners(const std::vector &signers)
{
kleo_assert(!d->job);
d->signers = signers;
}
void SignEncryptTask::setRecipients(const std::vector &recipients)
{
kleo_assert(!d->job);
d->recipients = recipients;
}
void SignEncryptTask::setOverwritePolicy(const std::shared_ptr &policy)
{
kleo_assert(!d->job);
d->m_overwritePolicy = policy;
}
void SignEncryptTask::setSign(bool sign)
{
kleo_assert(!d->job);
d->sign = sign;
}
void SignEncryptTask::setEncrypt(bool encrypt)
{
kleo_assert(!d->job);
d->encrypt = encrypt;
}
void SignEncryptTask::setDetachedSignature(bool detached)
{
kleo_assert(!d->job);
d->detached = detached;
}
void SignEncryptTask::setEncryptSymmetric(bool symmetric)
{
kleo_assert(!d->job);
d->symmetric = symmetric;
}
void SignEncryptTask::setClearsign(bool clearsign)
{
kleo_assert(!d->job);
d->clearsign = clearsign;
}
void SignEncryptTask::setCreateArchive(bool archive)
{
kleo_assert(!d->job);
d->archive = archive;
}
Protocol SignEncryptTask::protocol() const
{
if (d->sign && !d->signers.empty()) {
return d->signers.front().protocol();
}
if (d->encrypt || d->symmetric) {
if (!d->recipients.empty()) {
return d->recipients.front().protocol();
} else {
return GpgME::OpenPGP; // symmetric OpenPGP encryption
}
}
throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL), i18n("Cannot determine protocol for task"));
}
QString SignEncryptTask::label() const
{
if (!d->labelText.isEmpty()) {
return d->labelText;
}
return d->inputLabel();
}
QString SignEncryptTask::tag() const
{
return Formatting::displayName(protocol());
}
unsigned long long SignEncryptTask::inputSize() const
{
return d->input ? d->input->size() : 0U;
}
static bool archiveJobsCanBeUsed(GpgME::Protocol protocol)
{
return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported();
}
void SignEncryptTask::doStart()
{
kleo_assert(!d->job);
if (d->sign) {
kleo_assert(!d->signers.empty());
if (d->archive) {
kleo_assert(!d->detached && !d->clearsign);
}
}
const auto proto = protocol();
if (d->archive && archiveJobsCanBeUsed(proto)) {
d->startSignEncryptArchiveJob(proto);
} else {
d->startSignEncryptJob(proto);
}
}
QString SignEncryptTask::Private::inputLabel() const
{
if (input) {
return input->label();
}
if (!inputFileNames.empty()) {
const auto firstFile = QFileInfo{inputFileNames.front()}.fileName();
return inputFileNames.size() == 1 ? firstFile : i18nc(", ...", "%1, ...", firstFile);
}
return {};
}
QString SignEncryptTask::Private::outputLabel() const
{
return output ? output->label() : QFileInfo{outputFileName}.fileName();
}
bool SignEncryptTask::Private::removeExistingOutputFile()
{
if (QFile::exists(outputFileName)) {
bool fileRemoved = false;
// we should already have asked the user for overwrite permission
if (m_overwritePolicy && (m_overwritePolicy->policy() == OverwritePolicy::Overwrite)) {
qCDebug(KLEOPATRA_LOG) << __func__ << "going to remove file for overwriting" << outputFileName;
fileRemoved = QFile::remove(outputFileName);
if (!fileRemoved) {
qCDebug(KLEOPATRA_LOG) << __func__ << "removing file to overwrite failed";
}
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "we have no permission to overwrite" << outputFileName;
}
if (!fileRemoved) {
QMetaObject::invokeMethod(
q,
[this]() {
slotResult(nullptr, SigningResult{}, EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)});
},
Qt::QueuedConnection);
return false;
}
}
return true;
}
void SignEncryptTask::Private::startSignEncryptJob(GpgME::Protocol proto)
{
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (proto == GpgME::OpenPGP) {
// either input and output are both set (e.g. when encrypting the notepad),
// or they are both unset (when encrypting files)
kleo_assert((!input && !output) || (input && output));
} else {
kleo_assert(input);
if (!output) {
output = Output::createFromFile(outputFileName, m_overwritePolicy);
}
}
#else
kleo_assert(input);
if (!output) {
output = Output::createFromFile(outputFileName, m_overwritePolicy);
}
#endif
if (encrypt || symmetric) {
Context::EncryptionFlags flags{Context::None};
if (proto == GpgME::OpenPGP) {
flags = static_cast(flags | Context::AlwaysTrust);
}
if (symmetric) {
flags = static_cast(flags | Context::Symmetric);
qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag";
}
if (sign) {
std::unique_ptr job = createSignEncryptJob(proto);
kleo_assert(job.get());
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (proto == GpgME::OpenPGP && !input && !output) {
kleo_assert(inputFileNames.size() == 1);
job->setSigners(signers);
job->setRecipients(recipients);
job->setInputFile(inputFileNames.front());
job->setOutputFile(outputFileName);
job->setEncryptionFlags(flags);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
} else {
if (inputFileNames.size() == 1) {
job->setFileName(inputFileNames.front());
}
job->start(signers, recipients, input->ioDevice(), output->ioDevice(), flags);
}
#else
if (inputFileNames.size() == 1) {
job->setFileName(inputFileNames.front());
}
job->start(signers, recipients, input->ioDevice(), output->ioDevice(), flags);
#endif
this->job = job.release();
} else {
std::unique_ptr job = createEncryptJob(proto);
kleo_assert(job.get());
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (proto == GpgME::OpenPGP && !input && !output) {
kleo_assert(inputFileNames.size() == 1);
job->setRecipients(recipients);
job->setInputFile(inputFileNames.front());
job->setOutputFile(outputFileName);
job->setEncryptionFlags(flags);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
} else {
if (inputFileNames.size() == 1) {
job->setFileName(inputFileNames.front());
}
job->start(recipients, input->ioDevice(), output->ioDevice(), flags);
}
#else
if (inputFileNames.size() == 1) {
job->setFileName(inputFileNames.front());
}
job->start(recipients, input->ioDevice(), output->ioDevice(), flags);
#endif
this->job = job.release();
}
} else if (sign) {
std::unique_ptr job = createSignJob(proto);
kleo_assert(job.get());
kleo_assert(!(detached && clearsign));
const GpgME::SignatureMode sigMode = detached ? GpgME::Detached : clearsign ? GpgME::Clearsigned : GpgME::NormalSignatureMode;
#if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
if (proto == GpgME::OpenPGP && !input && !output) {
kleo_assert(inputFileNames.size() == 1);
job->setSigners(signers);
job->setInputFile(inputFileNames.front());
job->setOutputFile(outputFileName);
job->setSigningFlags(sigMode);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
} else {
job->start(signers, input->ioDevice(), output->ioDevice(), sigMode);
}
#else
job->start(signers, input->ioDevice(), output->ioDevice(), sigMode);
#endif
this->job = job.release();
} else {
kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!");
}
}
void SignEncryptTask::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->job) {
d->job->slotCancel();
}
}
std::unique_ptr SignEncryptTask::Private::createSignJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr signJob(backend->signJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(signJob.get());
connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
connect(signJob.get(), SIGNAL(result(GpgME::SigningResult, QByteArray)), q, SLOT(slotResult(GpgME::SigningResult)));
return signJob;
}
std::unique_ptr SignEncryptTask::Private::createSignEncryptJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr signEncryptJob(backend->signEncryptJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(signEncryptJob.get());
connect(signEncryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
connect(signEncryptJob.get(),
SIGNAL(result(GpgME::SigningResult, GpgME::EncryptionResult, QByteArray)),
q,
SLOT(slotResult(GpgME::SigningResult, GpgME::EncryptionResult)));
return signEncryptJob;
}
std::unique_ptr SignEncryptTask::Private::createEncryptJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr encryptJob(backend->encryptJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(encryptJob.get());
connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult, QByteArray)), q, SLOT(slotResult(GpgME::EncryptionResult)));
return encryptJob;
}
void SignEncryptTask::Private::startSignEncryptArchiveJob(GpgME::Protocol proto)
{
kleo_assert(!input);
kleo_assert(!output);
#if !QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME
output = Output::createFromFile(outputFileName, m_overwritePolicy);
#endif
const auto baseDirectory = heuristicBaseDirectory(inputFileNames);
if (baseDirectory.isEmpty()) {
throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", inputFileNames.join(QLatin1Char('\n'))));
}
qCDebug(KLEOPATRA_LOG) << "heuristicBaseDirectory(" << inputFileNames << ") ->" << baseDirectory;
const auto tempPaths = makeRelativeTo(baseDirectory, inputFileNames);
const auto relativePaths = std::vector{tempPaths.begin(), tempPaths.end()};
qCDebug(KLEOPATRA_LOG) << "relative paths:" << relativePaths;
if (encrypt || symmetric) {
Context::EncryptionFlags flags{Context::None};
if (proto == GpgME::OpenPGP) {
flags = static_cast(flags | Context::AlwaysTrust);
}
if (symmetric) {
flags = static_cast(flags | Context::Symmetric);
qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag";
}
if (sign) {
labelText = i18nc("@info", "Creating signed and encrypted archive ...");
std::unique_ptr job = createSignEncryptArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
#if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME
job->setSigners(signers);
job->setRecipients(recipients);
job->setInputPaths(relativePaths);
job->setOutputFile(outputFileName);
job->setEncryptionFlags(flags);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
#else
job->start(signers, recipients, relativePaths, output->ioDevice(), flags);
#endif
this->job = job.release();
} else {
labelText = i18nc("@info", "Creating encrypted archive ...");
std::unique_ptr job = createEncryptArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
#if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME
job->setRecipients(recipients);
job->setInputPaths(relativePaths);
job->setOutputFile(outputFileName);
job->setEncryptionFlags(flags);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
#else
job->start(recipients, relativePaths, output->ioDevice(), flags);
#endif
this->job = job.release();
}
} else if (sign) {
labelText = i18nc("@info", "Creating signed archive ...");
std::unique_ptr job = createSignArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
#if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME
job->setSigners(signers);
job->setInputPaths(relativePaths);
job->setOutputFile(outputFileName);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
#else
job->start(signers, relativePaths, output->ioDevice());
#endif
this->job = job.release();
} else {
kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!");
}
}
std::unique_ptr SignEncryptTask::Private::createSignArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr signJob(backend->signArchiveJob(q->asciiArmor()));
auto job = signJob.get();
kleo_assert(job);
connect(job, &QGpgME::SignArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::SignArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult) {
slotResult(job, signResult, EncryptionResult{});
});
return signJob;
}
std::unique_ptr SignEncryptTask::Private::createSignEncryptArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr signEncryptJob(backend->signEncryptArchiveJob(q->asciiArmor()));
auto job = signEncryptJob.get();
kleo_assert(job);
connect(job, &QGpgME::SignEncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::SignEncryptArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult, const GpgME::EncryptionResult &encryptResult) {
slotResult(job, signResult, encryptResult);
});
return signEncryptJob;
}
std::unique_ptr SignEncryptTask::Private::createEncryptArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr encryptJob(backend->encryptArchiveJob(q->asciiArmor()));
auto job = encryptJob.get();
kleo_assert(job);
connect(job, &QGpgME::EncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::EncryptArchiveJob::result, q, [this, job](const GpgME::EncryptionResult &encryptResult) {
slotResult(job, SigningResult{}, encryptResult);
});
return encryptJob;
}
void SignEncryptTask::Private::slotResult(const SigningResult &result)
{
slotResult(qobject_cast(q->sender()), result, EncryptionResult{});
}
void SignEncryptTask::Private::slotResult(const SigningResult &sresult, const EncryptionResult &eresult)
{
slotResult(qobject_cast(q->sender()), sresult, eresult);
}
void SignEncryptTask::Private::slotResult(const EncryptionResult &result)
{
slotResult(qobject_cast(q->sender()), SigningResult{}, result);
}
void SignEncryptTask::Private::slotResult(const QGpgME::Job *job, const SigningResult &sresult, const EncryptionResult &eresult)
{
qCDebug(KLEOPATRA_LOG) << q << __func__ << "job:" << job << "signing result:" << QGpgME::toLogString(sresult)
<< "encryption result:" << QGpgME::toLogString(eresult);
const AuditLogEntry auditLog = AuditLogEntry::fromJob(job);
bool outputCreated = false;
if (input && input->failed()) {
if (output) {
output->cancel();
}
q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO), i18n("Input error: %1", escape(input->errorString())), auditLog));
return;
} else if (sresult.error().code() || eresult.error().code()) {
if (output) {
output->cancel();
}
if (!outputFileName.isEmpty() && eresult.error().code() != GPG_ERR_EEXIST) {
// ensure that the output file is removed if the task was canceled or an error occurred;
// unless a "file exists" error occurred because this means that the file with the name
// of outputFileName wasn't created as result of this task
if (QFile::exists(outputFileName)) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Removing output file" << outputFileName << "after error or cancel";
if (!QFile::remove(outputFileName)) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Removing output file" << outputFileName << "failed";
}
}
}
} else {
try {
kleo_assert(!sresult.isNull() || !eresult.isNull());
if (output) {
output->finalize();
}
outputCreated = true;
if (input) {
input->finalize();
}
} catch (const GpgME::Exception &e) {
q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
}
}
const LabelAndError inputInfo{inputLabel(), input ? input->errorString() : QString{}};
const LabelAndError outputInfo{outputLabel(), output ? output->errorString() : QString{}};
q->emitResult(std::shared_ptr(new SignEncryptFilesResult(sresult, eresult, inputInfo, outputInfo, outputCreated, auditLog)));
}
QString SignEncryptFilesResult::overview() const
{
const QString files = formatInputOutputLabel(m_input.label, m_output.label, !m_outputCreated);
return files + QLatin1StringView(": ") + makeOverview(makeResultOverview(m_sresult, m_eresult));
}
QString SignEncryptFilesResult::details() const
{
return errorString();
}
GpgME::Error SignEncryptFilesResult::error() const
{
if (m_sresult.error().code()) {
return m_sresult.error();
}
if (m_eresult.error().code()) {
return m_eresult.error();
}
return {};
}
QString SignEncryptFilesResult::errorString() const
{
const bool sign = !m_sresult.isNull();
const bool encrypt = !m_eresult.isNull();
kleo_assert(sign || encrypt);
if (sign && encrypt) {
return m_sresult.error().code() ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString)
: m_eresult.error().code() ? makeResultDetails(m_eresult, m_input.errorString, m_output.errorString)
: QString();
}
return sign ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) //
: makeResultDetails(m_eresult, m_input.errorString, m_output.errorString);
}
Task::Result::VisualCode SignEncryptFilesResult::code() const
{
if (m_sresult.error().isCanceled() || m_eresult.error().isCanceled()) {
return Warning;
}
return (m_sresult.error().code() || m_eresult.error().code()) ? NeutralError : NeutralSuccess;
}
AuditLogEntry SignEncryptFilesResult::auditLog() const
{
return m_auditLog;
}
#include "moc_signencrypttask.cpp"
diff --git a/src/dialogs/setinitialpindialog.cpp b/src/dialogs/setinitialpindialog.cpp
index 474125a40..1dead4f78 100644
--- a/src/dialogs/setinitialpindialog.cpp
+++ b/src/dialogs/setinitialpindialog.cpp
@@ -1,199 +1,198 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/setinitialpindialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "setinitialpindialog.h"
#include "ui_setinitialpindialog.h"
#include
#include
#include
#include
-#include // for Qt::escape
#include
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
enum State {
Unknown = 0,
NotSet,
AlreadySet,
Ongoing,
Ok,
Failed,
NumStates,
};
const char *icons[] = {
// PENDING(marc) use better icons, once available
"", // Unknown
"", // NotSet
"security-medium", // AlreadySet
"movie-process-working-kde", // Ongoing
"security-high", // Ok
"security-low", // Failed
};
static_assert(sizeof icons / sizeof(*icons) == NumStates, "");
static_assert(sizeof("movie-") == 7, "");
static void update_widget(State state, bool delay, QLabel *resultLB, QLabel *lb, QPushButton *pb, QLabel *statusLB)
{
Q_ASSERT(state >= 0);
Q_ASSERT(state < NumStates);
const char *icon = icons[state];
if (qstrncmp(icon, "movie-", sizeof("movie-") - 1) == 0) {
resultLB->setMovie(KIconLoader::global()->loadMovie(QLatin1StringView(icon + sizeof("movie-")), KIconLoader::NoGroup));
} else if (icon && *icon) {
resultLB->setPixmap(QIcon::fromTheme(QLatin1StringView(icon)).pixmap(32));
} else {
resultLB->setPixmap(QPixmap());
}
lb->setEnabled((state == NotSet || state == Failed) && !delay);
pb->setEnabled((state == NotSet || state == Failed) && !delay);
if (state == AlreadySet) {
statusLB->setText(
xi18nc("@info", "No NullPin found. If this PIN was not set by you personally, the card might have been tampered with. "));
}
}
static QString format_error(const Error &err)
{
if (err.isCanceled()) {
return i18nc("@info", "Canceled setting PIN.");
}
if (err)
return xi18nc("@info", "There was an error setting the PIN: %1 .", Formatting::errorAsString(err).toHtmlEscaped());
else {
return i18nc("@info", "PIN set successfully.");
}
}
class SetInitialPinDialog::Private
{
friend class ::Kleo::Dialogs::SetInitialPinDialog;
SetInitialPinDialog *const q;
public:
explicit Private(SetInitialPinDialog *qq)
: q(qq)
, nksState(Unknown)
, sigGState(Unknown)
, ui(q)
{
}
private:
void slotNksButtonClicked()
{
nksState = Ongoing;
ui.nksStatusLB->clear();
updateWidgets();
Q_EMIT q->nksPinRequested();
}
void slotSigGButtonClicked()
{
sigGState = Ongoing;
ui.sigGStatusLB->clear();
updateWidgets();
Q_EMIT q->sigGPinRequested();
}
private:
void updateWidgets()
{
update_widget(nksState, false, ui.nksResultIcon, ui.nksLB, ui.nksPB, ui.nksStatusLB);
update_widget(sigGState, nksState == NotSet || nksState == Failed || nksState == Ongoing, ui.sigGResultIcon, ui.sigGLB, ui.sigGPB, ui.sigGStatusLB);
ui.closePB()->setEnabled(q->isComplete());
ui.cancelPB()->setEnabled(!q->isComplete());
}
private:
State nksState, sigGState;
struct UI : public Ui::SetInitialPinDialog {
explicit UI(Dialogs::SetInitialPinDialog *qq)
: Ui::SetInitialPinDialog()
{
setupUi(qq);
closePB()->setEnabled(false);
connect(closePB(), &QAbstractButton::clicked, qq, &QDialog::accept);
}
QAbstractButton *closePB() const
{
Q_ASSERT(dialogButtonBox);
return dialogButtonBox->button(QDialogButtonBox::Close);
}
QAbstractButton *cancelPB() const
{
Q_ASSERT(dialogButtonBox);
return dialogButtonBox->button(QDialogButtonBox::Cancel);
}
} ui;
};
SetInitialPinDialog::SetInitialPinDialog(QWidget *p)
: QDialog(p)
, d(new Private(this))
{
}
SetInitialPinDialog::~SetInitialPinDialog()
{
}
void SetInitialPinDialog::setNksPinPresent(bool on)
{
d->nksState = on ? AlreadySet : NotSet;
d->updateWidgets();
}
void SetInitialPinDialog::setSigGPinPresent(bool on)
{
d->sigGState = on ? AlreadySet : NotSet;
d->updateWidgets();
}
void SetInitialPinDialog::setNksPinSettingResult(const Error &err)
{
d->ui.nksStatusLB->setText(format_error(err));
d->nksState = (err.isCanceled() ? NotSet //
: err ? Failed
: Ok);
d->updateWidgets();
}
void SetInitialPinDialog::setSigGPinSettingResult(const Error &err)
{
d->ui.sigGStatusLB->setText(format_error(err));
d->sigGState = (err.isCanceled() ? NotSet //
: err ? Failed
: Ok);
d->updateWidgets();
}
bool SetInitialPinDialog::isComplete() const
{
return (d->nksState == Ok || d->nksState == AlreadySet);
}
#include "moc_setinitialpindialog.cpp"
diff --git a/src/main.cpp b/src/main.cpp
index 294cce2ab..71c279337 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,286 +1,285 @@
/*
main.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2001, 2002, 2004, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "aboutdata.h"
#include "kleopatraapplication.h"
#include "mainwindow.h"
#include "accessibility/accessiblewidgetfactory.h"
#include
#include
#include "utils/kuniqueservice.h"
#include "utils/userinfo.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kleopatra_debug.h"
#include "kleopatra_options.h"
#include
#include
#include
#include
#include
#include
#include
-#include // for Qt::escape
#include
#include
#include
#include
#include
#include
#include
#include
QElapsedTimer startupTimer;
static bool selfCheck()
{
Kleo::Commands::SelfTestCommand cmd(nullptr);
cmd.setAutoDelete(false);
cmd.setAutomaticMode(true);
QEventLoop loop;
QObject::connect(&cmd, &Kleo::Commands::SelfTestCommand::finished, &loop, &QEventLoop::quit);
QTimer::singleShot(0, &cmd, &Kleo::Command::start); // start() may Q_EMIT finished()...
loop.exec();
if (cmd.isCanceled()) {
return false;
} else {
return true;
}
}
static void fillKeyCache(Kleo::UiServer *server)
{
auto cmd = new Kleo::ReloadKeysCommand(nullptr);
QObject::connect(cmd, SIGNAL(finished()), server, SLOT(enableCryptoCommands()));
cmd->start();
}
int main(int argc, char **argv)
{
startupTimer.start();
#ifdef Q_OS_WIN
// note: requires https://invent.kde.org/frameworks/kiconthemes/-/merge_requests/109
qputenv("QT_QPA_PLATFORM", "windows:darkmode=2");
#endif
KleopatraApplication app(argc, argv);
// Set OrganizationDomain early as this is used to generate the service
// name that will be registered on the bus.
app.setOrganizationDomain(QStringLiteral("kde.org"));
STARTUP_TIMING << "Application created";
/* Create the unique service ASAP to prevent double starts if
* the application is started twice very quickly. */
KUniqueService service;
QObject::connect(&service, &KUniqueService::activateRequested, &app, &KleopatraApplication::slotActivateRequested);
QObject::connect(&app, &KleopatraApplication::setExitValue, &service, [&service](int i) {
service.setExitValue(i);
});
STARTUP_TIMING << "Service created";
KCrash::initialize();
QAccessible::installFactory(Kleo::accessibleWidgetFactory);
qCDebug(KLEOPATRA_LOG) << "Application created";
app.setWindowIcon(QIcon::fromTheme(QStringLiteral("kleopatra"), app.windowIcon()));
KLocalizedString::setApplicationDomain(QByteArrayLiteral("kleopatra"));
// Initialize GpgME
{
const GpgME::Error gpgmeInitError = GpgME::initializeLibrary(0);
if (gpgmeInitError) {
KMessageBox::error(nullptr,
xi18nc("@info",
"The version of the GpgME library you are running against "
"is older than the one that the GpgME++ library was built against. "
"Kleopatra will not function in this setting. "
"Please ask your administrator for help in resolving this issue. "),
i18nc("@title", "GpgME Too Old"));
return EXIT_FAILURE;
}
STARTUP_TIMING << "GPGME Initialized";
}
AboutData aboutData;
KAboutData::setApplicationData(aboutData);
if (Kleo::userIsElevated()) {
/* This is a safeguard against bugreports that something fails because
* of permission problems on windows. Some users still have the Windows
* Vista behavior of running things as Administrator. This can break
* GnuPG in horrible ways for example if a stale lockfile is left that
* can't be removed without another elevation.
*
* Note: This is not the same as running as root on Linux. Elevated means
* that you are temporarily running with the "normal" user environment but
* with elevated permissions.
* */
if (KMessageBox::warningContinueCancel(nullptr,
xi18nc("@info",
"Kleopatra cannot be run as adminstrator without "
"breaking file permissions in the GnuPG data folder. "
"To manage keys for other users please manage them as a normal user and "
"copy the AppData\\Roaming\\gnupg directory with proper permissions. ")
+ xi18n("Are you sure that you want to continue? "),
i18nc("@title", "Running as Administrator"))
!= KMessageBox::Continue) {
return EXIT_FAILURE;
}
qCWarning(KLEOPATRA_LOG) << "User is running with administrative permissions.";
}
// Delay init after KUniqueservice call as this might already
// have terminated us and so we can avoid overhead (e.g. keycache
// setup / systray icon).
app.init();
STARTUP_TIMING << "Application initialized";
QCommandLineParser parser;
aboutData.setupCommandLine(&parser);
kleopatra_options(&parser);
parser.process(QApplication::arguments());
aboutData.processCommandLine(&parser);
{
const unsigned int threads = QThreadPool::globalInstance()->maxThreadCount();
QThreadPool::globalInstance()->setMaxThreadCount(qMax(2U, threads));
}
Kleo::ChecksumDefinition::setInstallPath(Kleo::gpg4winInstallPath());
Kleo::ArchiveDefinition::setInstallPath(Kleo::gnupgInstallPath());
#ifndef DISABLE_UISERVER
int rc;
Kleo::UiServer *server = nullptr;
try {
server = new Kleo::UiServer(parser.value(QStringLiteral("uiserver-socket")));
STARTUP_TIMING << "UiServer created";
QObject::connect(server, &Kleo::UiServer::startKeyManagerRequested, &app, &KleopatraApplication::openOrRaiseMainWindow);
QObject::connect(server, &Kleo::UiServer::startConfigDialogRequested, &app, &KleopatraApplication::openOrRaiseConfigDialog);
#define REGISTER(Command) server->registerCommandFactory(std::shared_ptr(new Kleo::GenericAssuanCommandFactory))
REGISTER(CreateChecksumsCommand);
REGISTER(DecryptCommand);
REGISTER(DecryptFilesCommand);
REGISTER(DecryptVerifyFilesCommand);
REGISTER(EchoCommand);
REGISTER(EncryptCommand);
REGISTER(EncryptFilesCommand);
REGISTER(EncryptSignFilesCommand);
REGISTER(ImportFilesCommand);
REGISTER(PrepEncryptCommand);
REGISTER(PrepSignCommand);
REGISTER(SelectCertificateCommand);
REGISTER(SignCommand);
REGISTER(SignEncryptFilesCommand);
REGISTER(SignFilesCommand);
REGISTER(VerifyChecksumsCommand);
REGISTER(VerifyCommand);
REGISTER(VerifyFilesCommand);
#undef REGISTER
server->start();
STARTUP_TIMING << "UiServer started";
} catch (const std::exception &e) {
qCDebug(KLEOPATRA_LOG) << "Failed to start UI Server: " << e.what();
#ifdef Q_OS_WIN
// We should probably change the UIServer to be only run on Windows at all because
// only the Windows Explorer Plugin uses it. But the plan of GnuPG devs as of 2022 is to
// change the Windows Explorer Plugin to use the command line and then remove the
// UiServer for everyone.
QMessageBox::information(nullptr,
i18n("GPG UI Server Error"),
i18nc("This error message is only shown on Windows when the socket to communicate with "
"Windows Explorer could not be created. This often times means that the whole installation is "
"buggy. e.g. GnuPG is not installed at all.",
"The Kleopatra Windows Explorer Module could not be initialized. "
"The error given was: %1 "
"This likely means that there is a problem with your installation. Try reinstalling or "
"contact your Administrator for support. "
"You can try to continue to use Kleopatra but there might be other problems. ",
QString::fromUtf8(e.what()).toHtmlEscaped()));
#endif
}
#endif // DISABLE_UISERVER
const bool daemon = parser.isSet(QStringLiteral("daemon"));
if (!daemon && app.isSessionRestored()) {
app.restoreMainWindow();
}
if (!selfCheck()) {
return EXIT_FAILURE;
}
STARTUP_TIMING << "SelfCheck completed";
if (server) {
fillKeyCache(server);
}
#ifndef QT_NO_SYSTEMTRAYICON
app.startMonitoringSmartCard();
#endif
app.setIgnoreNewInstance(false);
if (!daemon) {
const QString err = app.newInstance(parser);
if (!err.isEmpty()) {
std::cerr << i18n("Invalid arguments: %1", err).toLocal8Bit().constData() << "\n";
return EXIT_FAILURE;
}
STARTUP_TIMING << "new instance created";
}
rc = app.exec();
app.setIgnoreNewInstance(true);
QObject::disconnect(server, &Kleo::UiServer::startKeyManagerRequested, &app, &KleopatraApplication::openOrRaiseMainWindow);
QObject::disconnect(server, &Kleo::UiServer::startConfigDialogRequested, &app, &KleopatraApplication::openOrRaiseConfigDialog);
if (server) {
server->stop();
server->waitForStopped();
delete server;
}
return rc;
}
diff --git a/src/selftest/gpgagentcheck.cpp b/src/selftest/gpgagentcheck.cpp
index 57a84be16..b80bf41ad 100644
--- a/src/selftest/gpgagentcheck.cpp
+++ b/src/selftest/gpgagentcheck.cpp
@@ -1,96 +1,94 @@
/* -*- mode: c++; c-basic-offset:4 -*-
selftest/gpgagentcheck.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "gpgagentcheck.h"
#include "implementation_p.h"
#include
#include
-#include // for Qt::escape
-
#include
using namespace Kleo;
using namespace Kleo::_detail;
using namespace GpgME;
namespace
{
class GpgAgentCheck : public SelfTestImplementation
{
public:
explicit GpgAgentCheck()
: SelfTestImplementation(i18nc("@title", "Gpg-Agent Connectivity"))
{
runTest();
}
void runTest()
{
m_skipped = true;
if (!hasFeature(AssuanEngineFeature, 0)) {
m_error = i18n("GpgME library too old");
m_explanation = i18nc("@info",
"Either the GpgME library itself is too old, "
"or the GpgME++ library was compiled against "
"an older GpgME that did not support connecting to gpg-agent.");
m_proposedFix = xi18nc("@info",
"Upgrade to gpgme 1.2.0 or higher, "
"and ensure that gpgme++ was compiled against it.");
} else if (ensureEngineVersion(GpgME::GpgConfEngine, 2, 1, 0)) {
// 2.1 starts the agent on demand and requires it. So for 2.1.0 we can assume
// autostart works and we don't need to care about the agent.
m_skipped = false;
m_passed = true;
return;
} else {
Error error;
const std::unique_ptr ctx = Context::createForEngine(AssuanEngine, &error);
if (!ctx.get()) {
m_error = i18n("GpgME does not support gpg-agent");
m_explanation = xi18nc("@info",
"The GpgME library is new "
"enough to support gpg-agent , "
"but does not seem to do so in this installation. "
"The error returned was: %1 . ",
Formatting::errorAsString(error).toHtmlEscaped());
// PENDING(marc) proposed fix?
} else {
m_skipped = false;
const Error error = ctx->assuanTransact("GETINFO version");
if (error) {
m_passed = false;
m_error = i18n("unexpected error");
m_explanation = xi18nc("@info",
"Unexpected error while asking gpg-agent "
"for its version. "
"The error returned was: %1 . ",
Formatting::errorAsString(error).toHtmlEscaped());
// PENDING(marc) proposed fix?
} else {
m_passed = true;
}
}
}
}
};
}
std::shared_ptr Kleo::makeGpgAgentConnectivitySelfTest()
{
return std::shared_ptr(new GpgAgentCheck);
}
diff --git a/src/selftest/uiservercheck.cpp b/src/selftest/uiservercheck.cpp
index 866206976..dcc1d0249 100644
--- a/src/selftest/uiservercheck.cpp
+++ b/src/selftest/uiservercheck.cpp
@@ -1,72 +1,71 @@
/* -*- mode: c++; c-basic-offset:4 -*-
selftest/uiservercheck.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "uiservercheck.h"
#include "implementation_p.h"
#include
#include
#include
-#include // for Qt::escape
#include
using namespace Kleo;
using namespace Kleo::_detail;
namespace
{
class UiServerCheck : public SelfTestImplementation
{
public:
explicit UiServerCheck()
: SelfTestImplementation(i18nc("@title", "UiServer Connectivity"))
{
runTest();
}
void runTest()
{
KleopatraClientCopy::Command command;
{
QEventLoop loop;
loop.connect(&command, SIGNAL(finished()), SLOT(quit()));
QMetaObject::invokeMethod(&command, "start", Qt::QueuedConnection);
loop.exec();
}
if (command.error()) {
m_passed = false;
m_error = i18n("not reachable");
m_explanation = xi18nc("@info", "Could not connect to UiServer: %1 ", command.errorString().toHtmlEscaped());
m_proposedFix = xi18nc("@info",
"Check that your firewall is not set to block local connections "
"(allow connections to localhost or 127.0.0.1 ). ");
} else if (command.serverPid() != QCoreApplication::applicationPid()) {
m_passed = false;
m_error = i18n("multiple instances");
m_explanation = xi18nc("@info", "It seems another Kleopatra is running (with process-id %1)", command.serverPid());
m_proposedFix = xi18nc("@info", "Quit any other running instances of Kleopatra .");
} else {
m_passed = true;
}
}
};
}
std::shared_ptr Kleo::makeUiServerConnectivitySelfTest()
{
return std::shared_ptr(new UiServerCheck);
}