diff --git a/src/commands/exportopenpgpcertstoservercommand.cpp b/src/commands/exportopenpgpcertstoservercommand.cpp index e63483323..7c3633d72 100644 --- a/src/commands/exportopenpgpcertstoservercommand.cpp +++ b/src/commands/exportopenpgpcertstoservercommand.cpp @@ -1,122 +1,123 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportopenpgpcertstoservercommand.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 <config-kleopatra.h> #include "exportopenpgpcertstoservercommand.h" #include "command_p.h" #include <Libkleo/GnuPG> #include <gpgme++/key.h> #include <KLocalizedString> #include <KMessageBox> using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(KeyListController *c) : GnuPGProcessCommand(c) { } ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) { } ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(const Key &key) : GnuPGProcessCommand(key) { } ExportOpenPGPCertsToServerCommand::~ExportOpenPGPCertsToServerCommand() {} bool ExportOpenPGPCertsToServerCommand::preStartHook(QWidget *parent) const { if (!haveKeyserverConfigured()) if (KMessageBox::warningContinueCancel(parent, xi18nc("@info", "<para>No OpenPGP directory services have been configured.</para>" "<para>Since none is configured, <application>Kleopatra</application> will use " "<resource>keys.gnupg.net</resource> as the server to export to.</para>" "<para>You can configure OpenPGP directory servers in <application>Kleopatra</application>'s " "configuration dialog.</para>" "<para>Do you want to continue with <resource>keys.gnupg.net</resource> " "as the server to export to?</para>"), i18nc("@title:window", "OpenPGP Certificate Export"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-export-openpgp-missing-keyserver")) != KMessageBox::Continue) { return false; } return KMessageBox::warningContinueCancel(parent, xi18nc("@info", "<para>When OpenPGP certificates have been exported to a public directory server, " "it is nearly impossible to remove them again.</para>" "<para>Before exporting your certificate to a public directory server, make sure that you " "have created a revocation certificate so you can revoke the certificate if needed later.</para>" "<para>Are you sure you want to continue?</para>"), i18nc("@title:window", "OpenPGP Certificate Export"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-export-openpgp-nonrevocable")) == KMessageBox::Continue; } QStringList ExportOpenPGPCertsToServerCommand::arguments() const { QStringList result; result << gpgPath(); if (!haveKeyserverConfigured()) { result << QStringLiteral("--keyserver") << QStringLiteral("keys.gnupg.net"); } result << QStringLiteral("--send-keys"); - Q_FOREACH (const Key &key, d->keys()) { + const auto keys = d->keys(); + for (const Key &key : keys) { result << QLatin1String(key.primaryFingerprint()); } return result; } QString ExportOpenPGPCertsToServerCommand::errorCaption() const { return i18nc("@title:window", "OpenPGP Certificate Export Error"); } QString ExportOpenPGPCertsToServerCommand::successCaption() const { return i18nc("@title:window", "OpenPGP Certificate Export Finished"); } QString ExportOpenPGPCertsToServerCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "<para>The GPG process that tried to export OpenPGP certificates " "ended prematurely because of an unexpected error.</para>" "<para>Please check the output of <icode>%1</icode> for details.</para>", args.join(QLatin1Char(' '))); } QString ExportOpenPGPCertsToServerCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "<para>An error occurred while trying to export OpenPGP certificates.</para> " "<para>The output from <command>%1</command> was: <message>%2</message></para>", args[0], errorString()); } QString ExportOpenPGPCertsToServerCommand::successMessage(const QStringList &) const { return i18nc("@info", "OpenPGP certificates exported successfully."); } diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp index a42c46825..c07d32ef5 100644 --- a/src/commands/importcertificatescommand.cpp +++ b/src/commands/importcertificatescommand.cpp @@ -1,675 +1,676 @@ /* -*- 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-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "importcertificatescommand.h" #include "importcertificatescommand_p.h" #include "certifycertificatecommand.h" #include "kleopatra_debug.h" #include <Libkleo/KeyListSortFilterProxyModel> #include <Libkleo/KeyCache> #include <Libkleo/Predicates> #include <Libkleo/Formatting> #include <Libkleo/Stl_Util> #include <QGpgME/KeyListJob> #include <QGpgME/Protocol> #include <QGpgME/ImportJob> #include <QGpgME/ImportFromKeyserverJob> #include <QGpgME/ChangeOwnerTrustJob> #include <gpgme++/global.h> #include <gpgme++/importresult.h> #include <gpgme++/context.h> #include <gpgme++/key.h> #include <gpgme++/keylistresult.h> #include <KLocalizedString> #include <KMessageBox> #include <QByteArray> #include <QEventLoop> #include <QString> #include <QWidget> #include <QTreeView> #include <QTextDocument> // for Qt::escape #include <memory> #include <algorithm> #include <map> #include <set> using namespace GpgME; using namespace Kleo; using namespace QGpgME; namespace { make_comparator_str(ByImportFingerprint, .fingerprint()); class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel { Q_OBJECT public: ImportResultProxyModel(const std::vector<ImportResult> &results, const QStringList &ids, QObject *parent = nullptr) : AbstractKeyListSortFilterProxyModel(parent) { updateFindCache(results, ids); } ~ImportResultProxyModel() override {} ImportResultProxyModel *clone() const override { // compiler-generated copy ctor is fine! return new ImportResultProxyModel(*this); } void setImportResults(const std::vector<ImportResult> &results, const QStringList &ids) { updateFindCache(results, ids); 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(FingerprintRole).toString(); // find information: const std::vector<Import>::const_iterator it = qBinaryFind(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint<std::less>()); 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(FingerprintRole).toString(); return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint<std::less>()); } private: void updateFindCache(const std::vector<ImportResult> &results, const QStringList &ids) { Q_ASSERT(results.size() == static_cast<unsigned>(ids.size())); m_importsByFingerprint.clear(); m_idsByFingerprint.clear(); m_results = results; for (unsigned int i = 0, end = results.size(); i != end; ++i) { const std::vector<Import> imports = results[i].imports(); m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end()); const QString &id = ids[i]; for (std::vector<Import>::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) { m_idsByFingerprint[it->fingerprint()].insert(id); } } std::sort(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), ByImportFingerprint<std::less>()); } private: mutable std::vector<Import> m_importsByFingerprint; mutable std::map< const char *, std::set<QString>, ByImportFingerprint<std::less> > m_idsByFingerprint; std::vector<ImportResult> m_results; }; } ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c) : Command::Private(qq, c), waitForMoreJobs(false), containedExternalCMSCerts(false), nonWorkingProtocols(), idsByJob(), jobs(), results(), ids() { } ImportCertificatesCommand::Private::~Private() {} #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() {} static QString format_ids(const QStringList &ids) { QStringList escapedIds; for (const QString &id : ids) { if (!id.isEmpty()) { escapedIds << id.toHtmlEscaped(); } } return escapedIds.join(QLatin1String("<br>")); } static QString make_tooltip(const QStringList &ids) { if (ids.empty()) { return QString(); } if (ids.size() == 1) if (ids.front().isEmpty()) { return QString(); } else return i18nc("@info:tooltip", "Imported Certificates from %1", ids.front().toHtmlEscaped()); else return i18nc("@info:tooltip", "Imported certificates from these sources:<br/>%1", format_ids(ids)); } void ImportCertificatesCommand::Private::setImportResultProxyModel(const std::vector<ImportResult> &results, const QStringList &ids) { if (std::none_of(results.cbegin(), results.cend(), std::mem_fn(&ImportResult::numConsidered))) { return; } q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"), new ImportResultProxyModel(results, ids), make_tooltip(ids)); if (QTreeView *const tv = qobject_cast<QTreeView *>(parentWidgetOrView())) { tv->expandAll(); } } int sum(const std::vector<ImportResult> &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<ImportResult> &res, const QString &id = QString()) { const KLocalizedString normalLine = ki18n("<tr><td align=\"right\">%1</td><td>%2</td></tr>"); const KLocalizedString boldLine = ki18n("<tr><td align=\"right\"><b>%1</b></td><td>%2</td></tr>"); const KLocalizedString headerLine = ki18n("<tr><th colspan=\"2\" align=\"center\">%1</th></tr>"); #define SUM( x ) sum( res, &ImportResult::x ) QStringList lines; if (!id.isEmpty()) { lines.push_back(headerLine.subs(id).toString()); } lines.push_back(normalLine.subs(i18n("Total number processed:")) .subs(SUM(numConsidered)).toString()); lines.push_back(normalLine.subs(i18n("Imported:")) .subs(SUM(numImported)).toString()); if (const int n = SUM(newSignatures)) lines.push_back(normalLine.subs(i18n("New signatures:")) .subs(n).toString()); if (const int n = SUM(newUserIDs)) lines.push_back(normalLine.subs(i18n("New user IDs:")) .subs(n).toString()); if (const int n = SUM(numKeysWithoutUserID)) lines.push_back(normalLine.subs(i18n("Certificates without user IDs:")) .subs(n).toString()); if (const int n = SUM(newSubkeys)) lines.push_back(normalLine.subs(i18n("New subkeys:")) .subs(n).toString()); if (const int n = SUM(newRevocations)) lines.push_back(boldLine.subs(i18n("Newly revoked:")) .subs(n).toString()); if (const int n = SUM(notImported)) lines.push_back(boldLine.subs(i18n("Not imported:")) .subs(n).toString()); if (const int n = SUM(numUnchanged)) lines.push_back(normalLine.subs(i18n("Unchanged:")) .subs(n).toString()); if (const int n = SUM(numSecretKeysConsidered)) lines.push_back(normalLine.subs(i18n("Secret keys processed:")) .subs(n).toString()); if (const int n = SUM(numSecretKeysImported)) lines.push_back(normalLine.subs(i18n("Secret keys imported:")) .subs(n).toString()); if (const int n = SUM(numSecretKeysConsidered) - SUM(numSecretKeysImported) - SUM(numSecretKeysUnchanged)) if (n > 0) lines.push_back(boldLine.subs(i18n("Secret keys <em>not</em> imported:")) .subs(n).toString()); if (const int n = SUM(numSecretKeysUnchanged)) lines.push_back(normalLine.subs(i18n("Secret keys unchanged:")) .subs(n).toString()); if (const int n = SUM(numV3KeysSkipped)) lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:")) .subs(n).toString()); #undef sum return lines.join(QString()); } static QString make_message_report(const std::vector<ImportResult> &res, const QStringList &ids) { Q_ASSERT(res.size() == static_cast<unsigned>(ids.size())); if (res.empty()) { return i18n("No imports (should not happen, please report a bug)."); } if (res.size() == 1) return ids.front().isEmpty() ? i18n("<qt><p>Detailed results of certificate import:</p>" "<table width=\"100%\">%1</table></qt>", make_report(res)) : i18n("<qt><p>Detailed results of importing %1:</p>" "<table width=\"100%\">%2</table></qt>", ids.front(), make_report(res)); return i18n("<qt><p>Detailed results of certificate import:</p>" "<table width=\"100%\">%1</table></qt>", make_report(res, i18n("Totals"))); } // Returns false on error, true if please certify was shown. bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp) { 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 = GpgME::Context::createForProtocol(GpgME::OpenPGP); if (!ctx) { // WTF qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto"; return false; } GpgME::Error err; auto key = ctx->key(fpr, err, false); delete ctx; if (key.isNull() || err) { // No such key most likely not OpenPGP 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 = QStringList() << i18n("A phone call to the person.") << i18n("Using a business card.") << i18n("Confirming it on a trusted website."); auto sel = KMessageBox::questionYesNo(parentWidgetOrView(), i18n("In order to mark the certificate as valid (green) it needs to be certified.") + QStringLiteral("<br>") + i18n("Certifying means that you check the Fingerprint.") + QStringLiteral("<br>") + i18n("Some suggestions to do this are:") + QStringLiteral("<li><ul>%1").arg(suggestions.join(QStringLiteral("</ul><ul>"))) + QStringLiteral("</ul></li>") + i18n("Do you wish to start this process now?"), i18nc("@title", "You have imported a new certificate (public key)"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("CertifyQuestion")); if (sel == KMessageBox::Yes) { QEventLoop loop; auto cmd = new Commands::CertifyCertificateCommand(key); cmd->setParentWidget(parentWidgetOrView()); loop.connect(cmd, SIGNAL(finished()), SLOT(quit())); QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection); loop.exec(); } return true; } void ImportCertificatesCommand::Private::showDetails(QWidget *parent, const std::vector<ImportResult> &res, const QStringList &ids) { if (res.size() == 1 && res[0].numImported() == 1 && res[0].imports().size() == 1) { if (showPleaseCertify(res[0].imports()[0])) { return; } } setImportResultProxyModel(res, ids); KMessageBox::information(parent, make_message_report(res, ids), i18n("Certificate Import Result")); } void ImportCertificatesCommand::Private::showDetails(const std::vector<ImportResult> &res, const QStringList &ids) { showDetails(parentWidgetOrView(), res, ids); } static QString make_error_message(const Error &err, const QString &id) { Q_ASSERT(err); Q_ASSERT(!err.isCanceled()); return id.isEmpty() ? i18n("<qt><p>An error occurred while trying " "to import the certificate:</p>" "<p><b>%1</b></p></qt>", QString::fromLocal8Bit(err.asString())) : i18n("<qt><p>An error occurred while trying " "to import the certificate %1:</p>" "<p><b>%2</b></p></qt>", id, QString::fromLocal8Bit(err.asString())); } void ImportCertificatesCommand::Private::showError(QWidget *parent, const Error &err, const QString &id) { if (parent) { KMessageBox::error(parent, make_error_message(err, id), i18n("Certificate Import Failed")); } else { showError(err, id); } } void ImportCertificatesCommand::Private::showError(const Error &err, const QString &id) { error(make_error_message(err, id), i18n("Certificate Import Failed")); } void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait) { if (wait == waitForMoreJobs) { return; } waitForMoreJobs = wait; tryToFinish(); } void ImportCertificatesCommand::Private::importResult(const ImportResult &result) { jobs.erase(std::remove(jobs.begin(), jobs.end(), q->sender()), jobs.end()); importResult(result, idsByJob[q->sender()]); } void ImportCertificatesCommand::Private::importResult(const ImportResult &result, const QString &id) { results.push_back(result); ids.push_back(id); tryToFinish(); } static void handleOwnerTrust(const std::vector<GpgME::ImportResult> &results) { //iterate over all imported certificates for (const ImportResult &result : results) { //when a new certificate got a secret key if (result.numSecretKeysImported() >= 1) { const char *fingerPr = result.imports()[0].fingerprint(); GpgME::Error err; QScopedPointer<Context> ctx(Context::createForProtocol(GpgME::Protocol::OpenPGP)); if (!ctx){ qCWarning(KLEOPATRA_LOG) << "Failed to get context"; continue; } const Key toTrustOwner = ctx->key(fingerPr, err , false); if (toTrustOwner.isNull()) { return; } QStringList uids; uids.reserve(toTrustOwner.userIDs().size()); Q_FOREACH (const UserID &uid, toTrustOwner.userIDs()) { uids << Formatting::prettyNameAndEMail(uid); } const QString str = xi18nc("@info", "<title>You have imported a Secret Key.</title>" "<para>The key has the fingerprint:<nl/>" "<numid>%1</numid>" "</para>" "<para>And claims the User IDs:" "<list><item>%2</item></list>" "</para>" "Is this your own key? (Set trust level to ultimate)", QString::fromUtf8(fingerPr), uids.join(QLatin1String("</item><item>"))); int k = KMessageBox::questionYesNo(nullptr, str, i18nc("@title:window", "Secret key imported")); if (k == KMessageBox::Yes) { //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); } } } } void ImportCertificatesCommand::Private::handleExternalCMSImports() { QStringList fingerprints; // 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. - Q_FOREACH (const ImportResult &result, results) { - Q_FOREACH (const Import &import, result.imports()) { + for (const ImportResult &result : qAsConst(results)) { + const auto imports = result.imports(); + for (const Import &import : imports) { if (!import.fingerprint()) { continue; } fingerprints << QString::fromLatin1(import.fingerprint()); } } auto job = QGpgME::smime()->keyListJob(false, true, true); // Old connect here because of Windows. connect(job, SIGNAL(result(GpgME::KeyListResult,std::vector<GpgME::Key>,QString,GpgME::Error)), q, SLOT(keyListDone(GpgME::KeyListResult,std::vector<GpgME::Key>,QString,GpgME::Error))); job->start(fingerprints, false); } void ImportCertificatesCommand::Private::keyListDone(const GpgME::KeyListResult &, const std::vector<GpgME::Key> &keys, const QString &, const GpgME::Error&) { KeyCache::mutableInstance()->refresh(keys); showDetails(results, ids); auto tv = dynamic_cast<QTreeView *> (view()); if (!tv) { qCDebug(KLEOPATRA_LOG) << "Failed to find treeview"; } else { tv->expandAll(); } finished(); } void ImportCertificatesCommand::Private::tryToFinish() { if (waitForMoreJobs || !jobs.empty()) { return; } if (std::any_of(results.cbegin(), results.cend(), [](const GpgME::ImportResult &result) { return result.error().code(); })) { setImportResultProxyModel(results, ids); if (std::all_of(results.cbegin(), results.cend(), [](const GpgME::ImportResult &result) { return result.error().isCanceled(); })) { Q_EMIT q->canceled(); } else { for (unsigned int i = 0, end = results.size(); i != end; ++i) if (const Error err = results[i].error()) { showError(err, ids[i]); } } } else { if (containedExternalCMSCerts) { handleExternalCMSImports(); // We emit finished and do show details // after the keylisting. return; } else { handleOwnerTrust(results); } showDetails(results, ids); } finished(); } static std::unique_ptr<ImportJob> 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<ImportJob>(backend->importJob()); } else { return std::unique_ptr<ImportJob>(); } } void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const QByteArray &data, const QString &id) { Q_ASSERT(protocol != UnknownProtocol); if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) { return; } std::unique_ptr<ImportJob> 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")); importResult(ImportResult(), id); return; } connect(job.get(), SIGNAL(result(GpgME::ImportResult)), q, SLOT(importResult(GpgME::ImportResult))); connect(job.get(), &Job::progress, q, &Command::progress); const GpgME::Error err = job->start(data); if (err.code()) { importResult(ImportResult(err), id); } else { jobs.push_back(job.release()); idsByJob[jobs.back()] = id; } } static std::unique_ptr<ImportFromKeyserverJob> 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<ImportFromKeyserverJob>(backend->importFromKeyserverJob()); } else { return std::unique_ptr<ImportFromKeyserverJob>(); } } void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const std::vector<Key> &keys, const QString &id) { Q_ASSERT(protocol != UnknownProtocol); if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) { return; } std::unique_ptr<ImportFromKeyserverJob> 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")); importResult(ImportResult(), id); return; } if (protocol == GpgME::CMS) { containedExternalCMSCerts = true; } connect(job.get(), SIGNAL(result(GpgME::ImportResult)), q, SLOT(importResult(GpgME::ImportResult))); connect(job.get(), &Job::progress, q, &Command::progress); const GpgME::Error err = job->start(keys); if (err.code()) { importResult(ImportResult(err), id); } else { jobs.push_back(job.release()); idsByJob[jobs.back()] = id; } } void ImportCertificatesCommand::doCancel() { std::for_each(d->jobs.begin(), d->jobs.end(), [](Job *job) { job->slotCancel(); }); } #undef d #undef q #include "moc_importcertificatescommand.cpp" #include "importcertificatescommand.moc" diff --git a/src/crypto/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp index f33b64e8e..33c88be2f 100644 --- a/src/crypto/autodecryptverifyfilescontroller.cpp +++ b/src/crypto/autodecryptverifyfilescontroller.cpp @@ -1,522 +1,522 @@ /* -*- mode: c++; c-basic-offset:4 -*- autodecryptverifyfilescontroller.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-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "autodecryptverifyfilescontroller.h" #include "fileoperationspreferences.h" #include <crypto/gui/decryptverifyoperationwidget.h> #include <crypto/gui/decryptverifyfilesdialog.h> #include <crypto/decryptverifytask.h> #include <crypto/taskcollection.h> #include "commands/decryptverifyfilescommand.h" #include <Libkleo/GnuPG> #include <utils/path-helper.h> #include <utils/input.h> #include <utils/output.h> #include <utils/kleo_assert.h> #include <utils/archivedefinition.h> #include <Libkleo/Classify> #include <KLocalizedString> #include <KMessageBox> #include "kleopatra_debug.h" #include <QDir> #include <QFile> #include <QFileInfo> #include <QTimer> #include <QFileDialog> #include <QTemporaryDir> using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; class AutoDecryptVerifyFilesController::Private { AutoDecryptVerifyFilesController *const q; public: explicit Private(AutoDecryptVerifyFilesController *qq); ~Private() { qCDebug(KLEOPATRA_LOG); delete m_workDir; } void slotDialogCanceled(); void schedule(); void exec(); std::vector<std::shared_ptr<Task> > buildTasks(const QStringList &, QStringList &); struct CryptoFile { QString baseName; QString fileName; GpgME::Protocol protocol = GpgME::UnknownProtocol; int classification = 0; std::shared_ptr<Output> output; }; QVector<CryptoFile> classifyAndSortFiles(const QStringList &files); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void cancelAllTasks(); QStringList m_passedFiles, m_filesAfterPreparation; std::vector<std::shared_ptr<const DecryptVerifyResult> > m_results; std::vector<std::shared_ptr<Task> > m_runnableTasks, m_completedTasks; std::shared_ptr<Task> m_runningTask; bool m_errorDetected = false; DecryptVerifyOperation m_operation = DecryptVerify; DecryptVerifyFilesDialog *m_dialog = nullptr; QTemporaryDir *m_workDir = nullptr; }; AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq) : q(qq) { qRegisterMetaType<VerificationResult>(); } void AutoDecryptVerifyFilesController::Private::slotDialogCanceled() { qCDebug(KLEOPATRA_LOG); } void AutoDecryptVerifyFilesController::Private::schedule() { if (!m_runningTask && !m_runnableTasks.empty()) { const std::shared_ptr<Task> t = m_runnableTasks.back(); m_runnableTasks.pop_back(); t->start(); m_runningTask = t; } if (!m_runningTask) { kleo_assert(m_runnableTasks.empty()); for (const std::shared_ptr<const DecryptVerifyResult> &i : qAsConst(m_results)) { Q_EMIT q->verificationResult(i->verificationResult()); } } } void AutoDecryptVerifyFilesController::Private::exec() { Q_ASSERT(!m_dialog); QStringList undetected; std::vector<std::shared_ptr<Task> > tasks = buildTasks(m_passedFiles, undetected); if (!undetected.isEmpty()) { // Since GpgME 1.7.0 Classification is supposed to be reliable // so we really can't do anything with this data. reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to find encrypted or signed data in one or more files.<nl/>" "You can manually select what to do with the files now.<nl/>" "If they contain signed or encrypted data please report a bug (see Help->Report Bug).")); auto cmd = new Commands::DecryptVerifyFilesCommand(undetected, nullptr, true); cmd->start(); } if (tasks.empty()) { q->emitDoneOrError(); return; } Q_ASSERT(m_runnableTasks.empty()); m_runnableTasks.swap(tasks); std::shared_ptr<TaskCollection> coll(new TaskCollection); - Q_FOREACH (const std::shared_ptr<Task> &i, m_runnableTasks) { + for (const std::shared_ptr<Task> &i : qAsConst(m_runnableTasks)) { q->connectTask(i); } coll->setTasks(m_runnableTasks); m_dialog = new DecryptVerifyFilesDialog(coll); m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles)); QTimer::singleShot(0, q, SLOT(schedule())); if (m_dialog->exec() == QDialog::Accepted && m_workDir) { // Without workdir there is nothing to move. const QDir workdir(m_workDir->path()); const QDir outDir(m_dialog->outputLocation()); bool overWriteAll = false; qCDebug(KLEOPATRA_LOG) << workdir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &fi: workdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)) { const auto inpath = fi.absoluteFilePath(); if (fi.isDir()) { // A directory. Assume that the input was an archive // and avoid directory merges by trying to find a non // existing directory. auto candidate = fi.baseName(); if (candidate.startsWith(QLatin1Char('-'))) { // Bug in GpgTar Extracts stdout passed archives to a dir named - candidate = QFileInfo(m_passedFiles.first()).baseName(); } QString suffix; QFileInfo ofi; int i = 0; do { ofi = QFileInfo(outDir.absoluteFilePath(candidate + suffix)); if (!ofi.exists()) { break; } suffix = QStringLiteral("_%1").arg(++i); } while (i < 1000); if (!moveDir(inpath, ofi.absoluteFilePath())) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move <filename>%1</filename> to <filename>%2</filename>.", inpath, ofi.absoluteFilePath())); } continue; } const auto outpath = outDir.absoluteFilePath(fi.fileName()); qCDebug(KLEOPATRA_LOG) << "Moving " << inpath << " to " << outpath; const QFileInfo ofi(outpath); if (ofi.exists()) { int sel = KMessageBox::No; if (!overWriteAll) { sel = KMessageBox::questionYesNoCancel(m_dialog, i18n("The file <b>%1</b> already exists.\n" "Overwrite?", outpath), i18n("Overwrite Existing File?"), KStandardGuiItem::overwrite(), KGuiItem(i18n("Overwrite All")), KStandardGuiItem::cancel()); } if (sel == KMessageBox::Cancel) { qCDebug(KLEOPATRA_LOG) << "Overwriting canceled for: " << outpath; continue; } if (sel == KMessageBox::No) { //Overwrite All overWriteAll = true; } if (!QFile::remove(outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to delete <filename>%1</filename>.", outpath)); continue; } } if (!QFile::rename(inpath, outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move <filename>%1</filename> to <filename>%2</filename>.", inpath, outpath)); } } } q->emitDoneOrError(); delete m_dialog; m_dialog = nullptr; } QVector<AutoDecryptVerifyFilesController::Private::CryptoFile> AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files) { const auto isSignature = [](int classification) -> bool { return mayBeDetachedSignature(classification) || mayBeOpaqueSignature(classification) || (classification & Class::TypeMask) == Class::ClearsignedMessage; }; QVector<CryptoFile> out; for (const auto &file : files) { CryptoFile cFile; cFile.fileName = file; cFile.baseName = file.left(file.length() - 4); cFile.classification = classify(file); cFile.protocol = findProtocol(cFile.classification); auto it = std::find_if(out.begin(), out.end(), [&cFile](const CryptoFile &other) { return other.protocol == cFile.protocol && other.baseName == cFile.baseName; }); if (it != out.end()) { // If we found a file with the same basename, make sure that encrypted // file is before the signature file, so that we first decrypt and then // verify if (isSignature(cFile.classification) && isCipherText(it->classification)) { out.insert(it + 1, cFile); } else if (isCipherText(cFile.classification) && isSignature(it->classification)) { out.insert(it, cFile); } else { // both are signatures or both are encrypted files, in which // case order does not matter out.insert(it, cFile); } } else { out.push_back(cFile); } } return out; } std::vector< std::shared_ptr<Task> > AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected) { // sort files so that we make sure we first decrypt and then verify QVector<CryptoFile> cryptoFiles = classifyAndSortFiles(fileNames); std::vector<std::shared_ptr<Task> > tasks; for (auto it = cryptoFiles.begin(), end = cryptoFiles.end(); it != end; ++it) { auto &cFile = (*it); QFileInfo fi(cFile.fileName); qCDebug(KLEOPATRA_LOG) << "classified" << cFile.fileName << "as" << printableClassification(cFile.classification); if (!fi.isReadable()) { reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("Cannot open <filename>%1</filename> for reading.", cFile.fileName)); continue; } if (mayBeAnyCertStoreType(cFile.classification)) { // Trying to verify a certificate. Possible because extensions are often similar // for PGP Keys. reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("The file <filename>%1</filename> contains certificates and can't be decrypted or verified.", cFile.fileName)); qCDebug(KLEOPATRA_LOG) << "reported error"; continue; } // We can't reliably detect CMS detached signatures, so we will try to do // our best to use the current file as a detached signature and fallback to // opaque signature otherwise. if (cFile.protocol == GpgME::CMS && mayBeDetachedSignature(cFile.classification)) { // First, see if previous task was a decryption task for the same file // and "pipe" it's output into our input std::shared_ptr<Input> input; bool prepend = false; if (it != cryptoFiles.begin()) { const auto prev = it - 1; if (prev->protocol == cFile.protocol && prev->baseName == cFile.baseName) { input = Input::createFromOutput(prev->output); prepend = true; } } if (!input) { if (QFile::exists(cFile.baseName)) { input = Input::createFromFile(cFile.baseName); } } if (input) { qCDebug(KLEOPATRA_LOG) << "Detached CMS verify: " << cFile.fileName; std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(input); t->setProtocol(cFile.protocol); if (prepend) { // Put the verify task BEFORE the decrypt task in the tasks queue, // because the tasks are executed in reverse order! tasks.insert(tasks.end() - 1, t); } else { tasks.push_back(t); } continue; } else { // No signed data, maybe not a detached signature } } if (isDetachedSignature(cFile.classification)) { // Detached signature, try to find data or ask the user. QString signedDataFileName = cFile.baseName; if (signedDataFileName.isEmpty()) { signedDataFileName = QFileDialog::getOpenFileName(nullptr, xi18n("Select the file to verify with \"%1\"", fi.fileName()), fi.dir().dirName()); } if (signedDataFileName.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify aborted."; } else { qCDebug(KLEOPATRA_LOG) << "Detached verify: " << cFile.fileName << " Data: " << signedDataFileName; std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(Input::createFromFile(signedDataFileName)); t->setProtocol(cFile.protocol); tasks.push_back(t); } continue; } if (!mayBeAnyMessageType(cFile.classification)) { // Not a Message? Maybe there is a signature for this file? const auto signatures = findSignatures(cFile.fileName); bool foundSig = false; if (!signatures.empty()) { for (const QString &sig : signatures) { const auto classification = classify(sig); qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << cFile.fileName << "Classification: " << classification; const auto proto = findProtocol(classification); if (proto == GpgME::UnknownProtocol) { qCDebug(KLEOPATRA_LOG) << "Could not determine protocol. Skipping guess."; continue; } foundSig = true; std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); t->setInput(Input::createFromFile(sig)); t->setSignedData(Input::createFromFile(cFile.fileName)); t->setProtocol(proto); tasks.push_back(t); } } if (!foundSig) { undetected << cFile.fileName; qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected."; } } else { // Any Message type so we have input and output. const auto input = Input::createFromFile(cFile.fileName); const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); const auto ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName); if (FileOperationsPreferences().dontUseTmpDir()) { if (!m_workDir) { m_workDir = new QTemporaryDir(heuristicBaseDirectory(fileNames) + QStringLiteral("/kleopatra-XXXXXX")); } if (!m_workDir->isValid()) { qCDebug(KLEOPATRA_LOG) << m_workDir->path() << "not a valid temporary directory."; delete m_workDir; m_workDir = new QTemporaryDir(); } } else if (!m_workDir) { m_workDir = new QTemporaryDir(); } qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory."; const auto wd = QDir(m_workDir->path()); const auto output = ad ? ad->createOutputFromUnpackCommand(cFile.protocol, cFile.fileName, wd) : /*else*/ Output::createFromFile(wd.absoluteFilePath(outputFileName(fi.fileName())), false); // If this might be opaque CMS signature, then try that. We already handled // detached CMS signature above const auto isCMSOpaqueSignature = cFile.protocol == GpgME::CMS && mayBeOpaqueSignature(cFile.classification); if (isOpaqueSignature(cFile.classification) || isCMSOpaqueSignature) { qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask"; std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask); t->setInput(input); t->setOutput(output); t->setProtocol(cFile.protocol); tasks.push_back(t); } else { // Any message. That is not an opaque signature needs to be // decrypted. Verify we always do because we can't know if // an encrypted message is also signed. qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask"; std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask); t->setInput(input); t->setOutput(output); t->setProtocol(cFile.protocol); cFile.output = output; tasks.push_back(t); } } } return tasks; } void AutoDecryptVerifyFilesController::setFiles(const QStringList &files) { d->m_passedFiles = files; } AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(QObject *parent) : DecryptVerifyFilesController(parent), d(new Private(this)) { } AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *parent) : DecryptVerifyFilesController(ctx, parent), d(new Private(this)) { } AutoDecryptVerifyFilesController::~AutoDecryptVerifyFilesController() { qCDebug(KLEOPATRA_LOG); } void AutoDecryptVerifyFilesController::start() { d->exec(); } void AutoDecryptVerifyFilesController::setOperation(DecryptVerifyOperation op) { d->m_operation = op; } DecryptVerifyOperation AutoDecryptVerifyFilesController::operation() const { return d->m_operation; } void AutoDecryptVerifyFilesController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. m_runnableTasks.clear(); // a cancel() will result in a call to if (m_runningTask) { m_runningTask->cancel(); } } void AutoDecryptVerifyFilesController::cancel() { qCDebug(KLEOPATRA_LOG); try { d->m_errorDetected = true; if (d->m_dialog) { d->m_dialog->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void AutoDecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result) { Q_ASSERT(task); Q_UNUSED(task) // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container d->m_completedTasks.push_back(d->m_runningTask); d->m_runningTask.reset(); if (const std::shared_ptr<const DecryptVerifyResult> &dvr = std::dynamic_pointer_cast<const DecryptVerifyResult>(result)) { d->m_results.push_back(dvr); } QTimer::singleShot(0, this, SLOT(schedule())); } #include "moc_autodecryptverifyfilescontroller.cpp" diff --git a/src/crypto/createchecksumscontroller.cpp b/src/crypto/createchecksumscontroller.cpp index cef89f009..6df947642 100644 --- a/src/crypto/createchecksumscontroller.cpp +++ b/src/crypto/createchecksumscontroller.cpp @@ -1,716 +1,719 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/createchecksumscontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "createchecksumscontroller.h" #include <utils/input.h> #include <utils/output.h> #include <utils/kleo_assert.h> #include <Libkleo/Stl_Util> #include <Libkleo/ChecksumDefinition> #include <Libkleo/Classify> #include <KLocalizedString> #include "kleopatra_debug.h" #include <QTemporaryFile> #include <KConfigGroup> #include <KSharedConfig> #include <QDialog> #include <QDialogButtonBox> #include <QLabel> #include <QListWidget> #include <QVBoxLayout> #include <QPointer> #include <QFileInfo> #include <QThread> #include <QMutex> #include <QProgressDialog> #include <QDir> #include <QProcess> #include <gpg-error.h> #include <deque> #include <map> #include <limits> #include <functional> using namespace Kleo; using namespace Kleo::Crypto; namespace { class ResultDialog : public QDialog { Q_OBJECT public: ResultDialog(const QStringList &created, const QStringList &errors, QWidget *parent = nullptr, Qt::WindowFlags f = {}) : QDialog(parent, f), createdLB(created.empty() ? i18nc("@info", "No checksum files have been created.") : i18nc("@info", "These checksum files have been successfully created:"), this), createdLW(this), errorsLB(errors.empty() ? i18nc("@info", "There were no errors.") : i18nc("@info", "The following errors were encountered:"), this), errorsLW(this), buttonBox(QDialogButtonBox::Ok, Qt::Horizontal, this), vlay(this) { KDAB_SET_OBJECT_NAME(createdLB); KDAB_SET_OBJECT_NAME(createdLW); KDAB_SET_OBJECT_NAME(errorsLB); KDAB_SET_OBJECT_NAME(errorsLW); KDAB_SET_OBJECT_NAME(buttonBox); KDAB_SET_OBJECT_NAME(vlay); createdLW.addItems(created); QRect r; for (int i = 0; i < created.size(); ++i) { r = r.united(createdLW.visualRect(createdLW.model()->index(0, i))); } createdLW.setMinimumWidth(qMin(1024, r.width() + 4 * createdLW.frameWidth())); errorsLW.addItems(errors); vlay.addWidget(&createdLB); vlay.addWidget(&createdLW, 1); vlay.addWidget(&errorsLB); vlay.addWidget(&errorsLW, 1); vlay.addWidget(&buttonBox); if (created.empty()) { createdLW.hide(); } if (errors.empty()) { errorsLW.hide(); } connect(&buttonBox, &QDialogButtonBox::accepted, this, &ResultDialog::accept); connect(&buttonBox, &QDialogButtonBox::rejected, this, &ResultDialog::reject); readConfig(); } ~ResultDialog() { writeConfig(); } void readConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "ResultDialog"); const QSize size = dialog.readEntry("Size", QSize(600, 400)); if (size.isValid()) { resize(size); } } void writeConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "ResultDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } private: QLabel createdLB; QListWidget createdLW; QLabel errorsLB; QListWidget errorsLW; QDialogButtonBox buttonBox; QVBoxLayout vlay; }; } #ifdef Q_OS_UNIX static const bool HAVE_UNIX = true; #else static const bool HAVE_UNIX = false; #endif static const Qt::CaseSensitivity fs_cs = HAVE_UNIX ? Qt::CaseSensitive : Qt::CaseInsensitive; // can we use QAbstractFileEngine::caseSensitive()? static QStringList fs_sort(QStringList l) { std::sort(l.begin(), l.end(), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, fs_cs) < 0; }); return l; } static QStringList fs_intersect(QStringList l1, QStringList l2) { fs_sort(l1); fs_sort(l2); QStringList result; std::set_intersection(l1.begin(), l1.end(), l2.begin(), l2.end(), std::back_inserter(result), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, fs_cs) < 0; }); return result; } static QList<QRegExp> get_patterns(const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions) { QList<QRegExp> result; for (const std::shared_ptr<ChecksumDefinition> &cd : checksumDefinitions) - if (cd) - Q_FOREACH (const QString &pattern, cd->patterns()) { + if (cd) { + const auto patterns = cd->patterns(); + for (const QString &pattern : patterns) { result.push_back(QRegExp(pattern, fs_cs)); } + } return result; } namespace { struct matches_any : std::unary_function<QString, bool> { const QList<QRegExp> m_regexps; explicit matches_any(const QList<QRegExp> ®exps) : m_regexps(regexps) {} bool operator()(const QString &s) const { return std::any_of(m_regexps.cbegin(), m_regexps.cend(), [s](const QRegExp &rx) { return rx.exactMatch(s); }); } }; } class CreateChecksumsController::Private : public QThread { Q_OBJECT friend class ::Kleo::Crypto::CreateChecksumsController; CreateChecksumsController *const q; public: explicit Private(CreateChecksumsController *qq); ~Private() override; Q_SIGNALS: void progress(int, int, const QString &); private: void slotOperationFinished() { #ifndef QT_NO_PROGRESSDIALOG if (progressDialog) { progressDialog->setValue(progressDialog->maximum()); progressDialog->close(); } #endif // QT_NO_PROGRESSDIALOG ResultDialog *const dlg = new ResultDialog(created, errors); dlg->setAttribute(Qt::WA_DeleteOnClose); q->bringToForeground(dlg); if (!errors.empty()) q->setLastError(gpg_error(GPG_ERR_GENERAL), errors.join(QLatin1Char('\n'))); q->emitDoneOrError(); } void slotProgress(int current, int total, const QString &what) { qCDebug(KLEOPATRA_LOG) << "progress: " << current << "/" << total << ": " << qPrintable(what); #ifndef QT_NO_PROGRESSDIALOG if (!progressDialog) { return; } progressDialog->setMaximum(total); progressDialog->setValue(current); progressDialog->setLabelText(what); #endif // QT_NO_PROGRESSDIALOG } private: void run() override; private: #ifndef QT_NO_PROGRESSDIALOG QPointer<QProgressDialog> progressDialog; #endif mutable QMutex mutex; const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions; std::shared_ptr<ChecksumDefinition> checksumDefinition; QStringList files; QStringList errors, created; bool allowAddition; volatile bool canceled; }; CreateChecksumsController::Private::Private(CreateChecksumsController *qq) : q(qq), #ifndef QT_NO_PROGRESSDIALOG progressDialog(), #endif mutex(), checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()), checksumDefinition(ChecksumDefinition::getDefaultChecksumDefinition(checksumDefinitions)), files(), errors(), created(), allowAddition(false), canceled(false) { connect(this, SIGNAL(progress(int,int,QString)), q, SLOT(slotProgress(int,int,QString))); connect(this, &Private::progress, q, &Controller::progress); connect(this, SIGNAL(finished()), q, SLOT(slotOperationFinished())); } CreateChecksumsController::Private::~Private() { qCDebug(KLEOPATRA_LOG); } CreateChecksumsController::CreateChecksumsController(QObject *p) : Controller(p), d(new Private(this)) { } CreateChecksumsController::CreateChecksumsController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *p) : Controller(ctx, p), d(new Private(this)) { } CreateChecksumsController::~CreateChecksumsController() { qCDebug(KLEOPATRA_LOG); } void CreateChecksumsController::setFiles(const QStringList &files) { kleo_assert(!d->isRunning()); kleo_assert(!files.empty()); const QList<QRegExp> patterns = get_patterns(d->checksumDefinitions); if (!std::all_of(files.cbegin(), files.cend(), matches_any(patterns)) && !std::none_of(files.cbegin(), files.cend(), matches_any(patterns))) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Create Checksums: input files must be either all checksum files or all files to be checksummed, not a mixture of both.")); } const QMutexLocker locker(&d->mutex); d->files = files; } void CreateChecksumsController::setAllowAddition(bool allow) { kleo_assert(!d->isRunning()); const QMutexLocker locker(&d->mutex); d->allowAddition = allow; } bool CreateChecksumsController::allowAddition() const { const QMutexLocker locker(&d->mutex); return d->allowAddition; } void CreateChecksumsController::start() { { const QMutexLocker locker(&d->mutex); #ifndef QT_NO_PROGRESSDIALOG d->progressDialog = new QProgressDialog(i18n("Initializing..."), i18n("Cancel"), 0, 0); applyWindowID(d->progressDialog); d->progressDialog->setAttribute(Qt::WA_DeleteOnClose); d->progressDialog->setMinimumDuration(1000); d->progressDialog->setWindowTitle(i18nc("@title:window", "Create Checksum Progress")); connect(d->progressDialog.data(), &QProgressDialog::canceled, this, &CreateChecksumsController::cancel); #endif // QT_NO_PROGRESSDIALOG d->canceled = false; d->errors.clear(); d->created.clear(); } d->start(); } void CreateChecksumsController::cancel() { qCDebug(KLEOPATRA_LOG); const QMutexLocker locker(&d->mutex); d->canceled = true; } namespace { struct Dir { QDir dir; QString sumFile; QStringList inputFiles; quint64 totalSize; std::shared_ptr<ChecksumDefinition> checksumDefinition; }; } static QStringList remove_checksum_files(QStringList l, const QList<QRegExp> &rxs) { QStringList::iterator end = l.end(); for (const QRegExp &rx : rxs) { end = std::remove_if(l.begin(), end, [rx](const QString &str) { return rx.exactMatch(str); }); } l.erase(end, l.end()); return l; } namespace { struct File { QString name; QByteArray checksum; bool binary; }; } static QString decode(const QString &encoded) { QString decoded; decoded.reserve(encoded.size()); bool shift = false; for (QChar ch : encoded) if (shift) { switch (ch.toLatin1()) { case '\\': decoded += QLatin1Char('\\'); break; case 'n': decoded += QLatin1Char('\n'); break; default: qCDebug(KLEOPATRA_LOG) << "invalid escape sequence" << '\\' << ch << "(interpreted as '" << ch << "')"; decoded += ch; break; } shift = false; } else { if (ch == QLatin1Char('\\')) { shift = true; } else { decoded += ch; } } return decoded; } static std::vector<File> parse_sum_file(const QString &fileName) { std::vector<File> files; QFile f(fileName); if (f.open(QIODevice::ReadOnly)) { QTextStream s(&f); QRegExp rx(QLatin1String("(\\?)([a-f0-9A-F]+) ([ *])([^\n]+)\n*")); while (!s.atEnd()) { const QString line = s.readLine(); if (rx.exactMatch(line)) { Q_ASSERT(!rx.cap(4).endsWith(QLatin1Char('\n'))); const File file = { rx.cap(1) == QLatin1String("\\") ? decode(rx.cap(4)) : rx.cap(4), rx.cap(2).toLatin1(), rx.cap(3) == QLatin1String("*"), }; files.push_back(file); } } } return files; } static quint64 aggregate_size(const QDir &dir, const QStringList &files) { quint64 n = 0; for (const QString &file : files) { n += QFileInfo(dir.absoluteFilePath(file)).size(); } return n; } static std::shared_ptr<ChecksumDefinition> filename2definition(const QString &fileName, const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions) { for (const std::shared_ptr<ChecksumDefinition> &cd : checksumDefinitions) { if (cd) { - Q_FOREACH (const QString &pattern, cd->patterns()) { + const auto patterns = cd->patterns(); + for (const QString &pattern : patterns) { if (QRegExp(pattern, fs_cs).exactMatch(fileName)) { return cd; } } } } return std::shared_ptr<ChecksumDefinition>(); } static std::vector<Dir> find_dirs_by_sum_files(const QStringList &files, bool allowAddition, const std::function<void(int)> &progress, const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions) { const QList<QRegExp> patterns = get_patterns(checksumDefinitions); std::vector<Dir> dirs; dirs.reserve(files.size()); int i = 0; for (const QString &file : files) { const QFileInfo fi(file); const QDir dir = fi.dir(); const QStringList entries = remove_checksum_files(dir.entryList(QDir::Files), patterns); QStringList inputFiles; if (allowAddition) { inputFiles = entries; } else { const std::vector<File> parsed = parse_sum_file(fi.absoluteFilePath()); QStringList oldInputFiles; oldInputFiles.reserve(parsed.size()); std::transform(parsed.cbegin(), parsed.cend(), std::back_inserter(oldInputFiles), std::mem_fn(&File::name)); inputFiles = fs_intersect(oldInputFiles, entries); } const Dir item = { dir, fi.fileName(), inputFiles, aggregate_size(dir, inputFiles), filename2definition(fi.fileName(), checksumDefinitions) }; dirs.push_back(item); if (progress) { progress(++i); } } return dirs; } namespace { struct less_dir : std::binary_function<QDir, QDir, bool> { bool operator()(const QDir &lhs, const QDir &rhs) const { return QString::compare(lhs.absolutePath(), rhs.absolutePath(), fs_cs) < 0; } }; } static std::vector<Dir> find_dirs_by_input_files(const QStringList &files, const std::shared_ptr<ChecksumDefinition> &checksumDefinition, bool allowAddition, const std::function<void(int)> &progress, const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions) { Q_UNUSED(allowAddition) if (!checksumDefinition) { return std::vector<Dir>(); } const QList<QRegExp> patterns = get_patterns(checksumDefinitions); std::map<QDir, QStringList, less_dir> dirs2files; // Step 1: sort files by the dir they're contained in: std::deque<QString> inputs(files.begin(), files.end()); int i = 0; while (!inputs.empty()) { const QString file = inputs.front(); inputs.pop_front(); const QFileInfo fi(file); if (fi.isDir()) { QDir dir(file); dirs2files[ dir ] = remove_checksum_files(dir.entryList(QDir::Files), patterns); const auto entryList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); std::transform(entryList.cbegin(), entryList.cend(), std::inserter(inputs, inputs.begin()), [&dir](const QString &entry) { return dir.absoluteFilePath(entry); }); } else { dirs2files[fi.dir()].push_back(file); } if (progress) { progress(++i); } } // Step 2: convert into vector<Dir>: std::vector<Dir> dirs; dirs.reserve(dirs2files.size()); for (std::map<QDir, QStringList, less_dir>::const_iterator it = dirs2files.begin(), end = dirs2files.end(); it != end; ++it) { const QStringList inputFiles = remove_checksum_files(it->second, patterns); if (inputFiles.empty()) { continue; } const Dir dir = { it->first, checksumDefinition->outputFileName(), inputFiles, aggregate_size(it->first, inputFiles), checksumDefinition }; dirs.push_back(dir); if (progress) { progress(++i); } } return dirs; } static QString process(const Dir &dir, bool *fatal) { const QString absFilePath = dir.dir.absoluteFilePath(dir.sumFile); QTemporaryFile out; QProcess p; if (!out.open()) { return QStringLiteral("Failed to open Temporary file."); } p.setWorkingDirectory(dir.dir.absolutePath()); p.setStandardOutputFile(out.fileName()); const QString program = dir.checksumDefinition->createCommand(); dir.checksumDefinition->startCreateCommand(&p, dir.inputFiles); p.waitForFinished(); qCDebug(KLEOPATRA_LOG) << "[" << &p << "] Exit code " << p.exitCode(); if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) { if (fatal && p.error() == QProcess::FailedToStart) { *fatal = true; } if (p.error() == QProcess::UnknownError) return i18n("Error while running %1: %2", program, QString::fromLocal8Bit(p.readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", program, p.errorString()); } } QFileInfo fi(absFilePath); if (!(fi.exists() && !QFile::remove(absFilePath)) && QFile::copy(out.fileName(), absFilePath)) { return QString(); } return xi18n("Failed to overwrite <filename>%1</filename>.", dir.sumFile); } namespace { static QDebug operator<<(QDebug s, const Dir &dir) { return s << "Dir(" << dir.dir << "->" << dir.sumFile << "<-(" << dir.totalSize << ')' << dir.inputFiles << ")\n"; } } void CreateChecksumsController::Private::run() { QMutexLocker locker(&mutex); const QStringList files = this->files; const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions = this->checksumDefinitions; const std::shared_ptr<ChecksumDefinition> checksumDefinition = this->checksumDefinition; const bool allowAddition = this->allowAddition; locker.unlock(); QStringList errors; QStringList created; if (!checksumDefinition) { errors.push_back(i18n("No checksum programs defined.")); locker.relock(); this->errors = errors; return; } else { qCDebug(KLEOPATRA_LOG) << "using checksum-definition" << checksumDefinition->id(); } // // Step 1: build a list of work to do (no progress): // const QString scanning = i18n("Scanning directories..."); Q_EMIT progress(0, 0, scanning); const bool haveSumFiles = std::all_of(files.cbegin(), files.cend(), matches_any(get_patterns(checksumDefinitions))); const auto progressCb = [this, &scanning](int c) { Q_EMIT progress(c, 0, scanning); }; const std::vector<Dir> dirs = haveSumFiles ? find_dirs_by_sum_files(files, allowAddition, progressCb, checksumDefinitions) : find_dirs_by_input_files(files, checksumDefinition, allowAddition, progressCb, checksumDefinitions); for (const Dir &dir : dirs) { qCDebug(KLEOPATRA_LOG) << dir; } if (!canceled) { Q_EMIT progress(0, 0, i18n("Calculating total size...")); const quint64 total = kdtools::accumulate_transform(dirs.cbegin(), dirs.cend(), std::mem_fn(&Dir::totalSize), Q_UINT64_C(0)); if (!canceled) { // // Step 2: perform work (with progress reporting): // // re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...) const quint64 factor = total / std::numeric_limits<int>::max() + 1; quint64 done = 0; - Q_FOREACH (const Dir &dir, dirs) { + for (const Dir &dir : dirs) { Q_EMIT progress(done / factor, total / factor, i18n("Checksumming (%2) in %1", dir.checksumDefinition->label(), dir.dir.path())); bool fatal = false; const QString error = process(dir, &fatal); if (!error.isEmpty()) { errors.push_back(error); } else { created.push_back(dir.dir.absoluteFilePath(dir.sumFile)); } done += dir.totalSize; if (fatal || canceled) { break; } } Q_EMIT progress(done / factor, total / factor, i18n("Done.")); } } locker.relock(); this->errors = errors; this->created = created; // mutex unlocked by QMutexLocker } #include "moc_createchecksumscontroller.cpp" #include "createchecksumscontroller.moc" diff --git a/src/crypto/decryptverifyemailcontroller.cpp b/src/crypto/decryptverifyemailcontroller.cpp index 804ddc859..7332f66fd 100644 --- a/src/crypto/decryptverifyemailcontroller.cpp +++ b/src/crypto/decryptverifyemailcontroller.cpp @@ -1,479 +1,479 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifyemailcontroller.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 <config-kleopatra.h> #include "decryptverifyemailcontroller.h" #include "kleopatra_debug.h" #include "emailoperationspreferences.h" #include <crypto/gui/newresultpage.h> #include <crypto/decryptverifytask.h> #include <crypto/taskcollection.h> #include <Libkleo/GnuPG> #include <utils/input.h> #include <utils/output.h> #include <utils/kleo_assert.h> #include <QGpgME/Protocol> #include <Libkleo/Formatting> #include <Libkleo/Classify> #include <kmime/kmime_header_parsing.h> #include <KLocalizedString> #include <QPoint> #include <QPointer> #include <QTimer> using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace KMime::Types; namespace { class DecryptVerifyEMailWizard : public QWizard { Q_OBJECT public: explicit DecryptVerifyEMailWizard(QWidget *parent = nullptr, Qt::WindowFlags f = {}) : QWizard(parent, f), m_resultPage(this) { KDAB_SET_OBJECT_NAME(m_resultPage); m_resultPage.setSubTitle(i18n("Status and progress of the crypto operations is shown here.")); addPage(&m_resultPage); } void addTaskCollection(const std::shared_ptr<TaskCollection> &coll) { m_resultPage.addTaskCollection(coll); } public Q_SLOTS: void accept() override { EMailOperationsPreferences prefs; prefs.setDecryptVerifyPopupGeometry(geometry()); prefs.save(); QWizard::accept(); } private: NewResultPage m_resultPage; }; } class DecryptVerifyEMailController::Private { DecryptVerifyEMailController *const q; public: explicit Private(DecryptVerifyEMailController *qq); void slotWizardCanceled(); void schedule(); std::vector<std::shared_ptr<AbstractDecryptVerifyTask> > buildTasks(); static DecryptVerifyEMailWizard *findOrCreateWizard(unsigned int id); void ensureWizardCreated(); void ensureWizardVisible(); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void cancelAllTasks(); std::vector<std::shared_ptr<Input> > m_inputs, m_signedDatas; std::vector<std::shared_ptr<Output> > m_outputs; unsigned int m_sessionId; QPointer<DecryptVerifyEMailWizard> m_wizard; std::vector<std::shared_ptr<const DecryptVerifyResult> > m_results; std::vector<std::shared_ptr<AbstractDecryptVerifyTask> > m_runnableTasks, m_completedTasks; std::shared_ptr<AbstractDecryptVerifyTask> m_runningTask; bool m_silent; bool m_operationCompleted; DecryptVerifyOperation m_operation; Protocol m_protocol; VerificationMode m_verificationMode; std::vector<KMime::Types::Mailbox> m_informativeSenders; }; DecryptVerifyEMailController::Private::Private(DecryptVerifyEMailController *qq) : q(qq), m_sessionId(0), m_silent(false), m_operationCompleted(false), m_operation(DecryptVerify), m_protocol(UnknownProtocol), m_verificationMode(Detached) { qRegisterMetaType<VerificationResult>(); } void DecryptVerifyEMailController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG); if (!m_operationCompleted) { reportError(gpg_error(GPG_ERR_CANCELED), i18n("User canceled")); } } void DecryptVerifyEMailController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result) { Q_ASSERT(task); // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container if (task == d->m_runningTask.get()) { d->m_completedTasks.push_back(d->m_runningTask); const std::shared_ptr<const DecryptVerifyResult> &dvr = std::dynamic_pointer_cast<const DecryptVerifyResult>(result); Q_ASSERT(dvr); d->m_results.push_back(dvr); d->m_runningTask.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } void DecryptVerifyEMailController::Private::schedule() { if (!m_runningTask && !m_runnableTasks.empty()) { const std::shared_ptr<AbstractDecryptVerifyTask> t = m_runnableTasks.back(); m_runnableTasks.pop_back(); t->start(); m_runningTask = t; } if (!m_runningTask) { kleo_assert(m_runnableTasks.empty()); - Q_FOREACH (const std::shared_ptr<const DecryptVerifyResult> &i, m_results) { + for (const std::shared_ptr<const DecryptVerifyResult> &i : qAsConst(m_results)) { Q_EMIT q->verificationResult(i->verificationResult()); } // if there is a popup, wait for either the client cancel or the user closing the popup. // Otherwise (silent case), finish immediately m_operationCompleted = true; q->emitDoneOrError(); } } void DecryptVerifyEMailController::Private::ensureWizardCreated() { if (m_wizard) { return; } DecryptVerifyEMailWizard *w = findOrCreateWizard(m_sessionId); connect(w, SIGNAL(destroyed()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); m_wizard = w; } namespace { template <typename C> void collectGarbage(C &c) { typename C::iterator it = c.begin(); while (it != c.end() /*sic!*/) if (it->second) { ++it; } else { c.erase(it++ /*sic!*/); } } } // static DecryptVerifyEMailWizard *DecryptVerifyEMailController::Private::findOrCreateWizard(unsigned int id) { static std::map<unsigned int, QPointer<DecryptVerifyEMailWizard> > s_wizards; collectGarbage(s_wizards); qCDebug(KLEOPATRA_LOG) << "id = " << id; if (id != 0) { const std::map<unsigned int, QPointer<DecryptVerifyEMailWizard> >::const_iterator it = s_wizards.find(id); if (it != s_wizards.end()) { Q_ASSERT(it->second && "This should have been garbage-collected"); return it->second; } } DecryptVerifyEMailWizard *w = new DecryptVerifyEMailWizard; w->setWindowTitle(i18nc("@title:window", "Decrypt/Verify E-Mail")); w->setAttribute(Qt::WA_DeleteOnClose); const QRect preferredGeometry = EMailOperationsPreferences().decryptVerifyPopupGeometry(); if (preferredGeometry.isValid()) { w->setGeometry(preferredGeometry); } s_wizards[id] = w; return w; } std::vector< std::shared_ptr<AbstractDecryptVerifyTask> > DecryptVerifyEMailController::Private::buildTasks() { const uint numInputs = m_inputs.size(); const uint numMessages = m_signedDatas.size(); const uint numOutputs = m_outputs.size(); const uint numInformativeSenders = m_informativeSenders.size(); // these are duplicated from DecryptVerifyCommandEMailBase::Private::checkForErrors with slightly modified error codes/messages if (!numInputs) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), i18n("At least one input needs to be provided")); if (numInformativeSenders > 0 && numInformativeSenders != numInputs) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), //TODO use better error code if possible i18n("Informative sender/signed data count mismatch")); if (numMessages) { if (numMessages != numInputs) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), //TODO use better error code if possible i18n("Signature/signed data count mismatch")); else if (m_operation != Verify || m_verificationMode != Detached) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), i18n("Signed data can only be given for detached signature verification")); } if (numOutputs) { if (numOutputs != numInputs) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), //TODO use better error code if possible i18n("Input/Output count mismatch")); else if (numMessages) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), i18n("Cannot use output and signed data simultaneously")); } kleo_assert(m_protocol != UnknownProtocol); const QGpgME::Protocol *const backend = (m_protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); if (!backend) { throw Kleo::Exception(makeGnuPGError(GPG_ERR_UNSUPPORTED_PROTOCOL), i18n("No backend support for %1", Formatting::displayName(m_protocol))); } if (m_operation != Decrypt && !m_silent) { ensureWizardVisible(); } std::vector< std::shared_ptr<AbstractDecryptVerifyTask> > tasks; for (unsigned int i = 0; i < numInputs; ++i) { std::shared_ptr<AbstractDecryptVerifyTask> task; switch (m_operation) { case Decrypt: { std::shared_ptr<DecryptTask> t(new DecryptTask); t->setInput(m_inputs.at(i)); Q_ASSERT(numOutputs); t->setOutput(m_outputs.at(i)); t->setProtocol(m_protocol); task = t; } break; case Verify: { if (m_verificationMode == Detached) { std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask); t->setInput(m_inputs.at(i)); t->setSignedData(m_signedDatas.at(i)); if (numInformativeSenders > 0) { t->setInformativeSender(m_informativeSenders.at(i)); } t->setProtocol(m_protocol); task = t; } else { std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask); t->setInput(m_inputs.at(i)); if (numOutputs) { t->setOutput(m_outputs.at(i)); } if (numInformativeSenders > 0) { t->setInformativeSender(m_informativeSenders.at(i)); } t->setProtocol(m_protocol); task = t; } } break; case DecryptVerify: { std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask); t->setInput(m_inputs.at(i)); Q_ASSERT(numOutputs); t->setOutput(m_outputs.at(i)); if (numInformativeSenders > 0) { t->setInformativeSender(m_informativeSenders.at(i)); } t->setProtocol(m_protocol); task = t; } } Q_ASSERT(task); tasks.push_back(task); } return tasks; } void DecryptVerifyEMailController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(m_wizard); } DecryptVerifyEMailController::DecryptVerifyEMailController(QObject *parent) : Controller(parent), d(new Private(this)) { } DecryptVerifyEMailController::DecryptVerifyEMailController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *parent) : Controller(ctx, parent), d(new Private(this)) { } DecryptVerifyEMailController::~DecryptVerifyEMailController() { qCDebug(KLEOPATRA_LOG); } void DecryptVerifyEMailController::start() { d->m_runnableTasks = d->buildTasks(); const std::shared_ptr<TaskCollection> coll(new TaskCollection); std::vector<std::shared_ptr<Task> > tsks; Q_FOREACH (const std::shared_ptr<Task> &i, d->m_runnableTasks) { connectTask(i); tsks.push_back(i); } coll->setTasks(tsks); d->ensureWizardCreated(); d->m_wizard->addTaskCollection(coll); d->ensureWizardVisible(); QTimer::singleShot(0, this, SLOT(schedule())); } void DecryptVerifyEMailController::setInput(const std::shared_ptr<Input> &input) { d->m_inputs.resize(1, input); } void DecryptVerifyEMailController::setInputs(const std::vector<std::shared_ptr<Input> > &inputs) { d->m_inputs = inputs; } void DecryptVerifyEMailController::setSignedData(const std::shared_ptr<Input> &data) { d->m_signedDatas.resize(1, data); } void DecryptVerifyEMailController::setSignedData(const std::vector<std::shared_ptr<Input> > &data) { d->m_signedDatas = data; } void DecryptVerifyEMailController::setOutput(const std::shared_ptr<Output> &output) { d->m_outputs.resize(1, output); } void DecryptVerifyEMailController::setOutputs(const std::vector<std::shared_ptr<Output> > &outputs) { d->m_outputs = outputs; } void DecryptVerifyEMailController::setInformativeSenders(const std::vector<KMime::Types::Mailbox> &senders) { d->m_informativeSenders = senders; } void DecryptVerifyEMailController::setWizardShown(bool shown) { d->m_silent = !shown; if (d->m_wizard) { d->m_wizard->setVisible(shown); } } void DecryptVerifyEMailController::setOperation(DecryptVerifyOperation operation) { d->m_operation = operation; } void DecryptVerifyEMailController::setVerificationMode(VerificationMode vm) { d->m_verificationMode = vm; } void DecryptVerifyEMailController::setProtocol(Protocol prot) { d->m_protocol = prot; } void DecryptVerifyEMailController::setSessionId(unsigned int id) { qCDebug(KLEOPATRA_LOG) << "id = " << id; d->m_sessionId = id; } void DecryptVerifyEMailController::cancel() { qCDebug(KLEOPATRA_LOG); try { if (d->m_wizard) { disconnect(d->m_wizard); d->m_wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void DecryptVerifyEMailController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. m_runnableTasks.clear(); // a cancel() will result in a call to if (m_runningTask) { m_runningTask->cancel(); } } #include "decryptverifyemailcontroller.moc" #include "moc_decryptverifyemailcontroller.cpp" diff --git a/src/crypto/encryptemailcontroller.cpp b/src/crypto/encryptemailcontroller.cpp index 4040c7c31..be544f910 100644 --- a/src/crypto/encryptemailcontroller.cpp +++ b/src/crypto/encryptemailcontroller.cpp @@ -1,313 +1,313 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/encryptemailcontroller.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 <config-kleopatra.h> #include "encryptemailcontroller.h" #include "kleopatra_debug.h" #include "encryptemailtask.h" #include "taskcollection.h" #include <crypto/gui/encryptemailwizard.h> #include <utils/input.h> #include <utils/output.h> #include <utils/kleo_assert.h> #include <Libkleo/Stl_Util> #include <Libkleo/KleoException> #include "emailoperationspreferences.h" #include <gpgme++/key.h> #include <kmime/kmime_header_parsing.h> #include <KLocalizedString> #include <QPointer> #include <QTimer> using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace GpgME; using namespace KMime::Types; class EncryptEMailController::Private { friend class ::Kleo::Crypto::EncryptEMailController; EncryptEMailController *const q; public: explicit Private(Mode mode, EncryptEMailController *qq); private: void slotWizardRecipientsResolved(); void slotWizardCanceled(); private: void ensureWizardCreated() const; void ensureWizardVisible(); void cancelAllTasks(); void schedule(); std::shared_ptr<EncryptEMailTask> takeRunnable(GpgME::Protocol proto); private: const Mode mode; std::vector< std::shared_ptr<EncryptEMailTask> > runnable, completed; std::shared_ptr<EncryptEMailTask> cms, openpgp; mutable QPointer<EncryptEMailWizard> wizard; }; EncryptEMailController::Private::Private(Mode m, EncryptEMailController *qq) : q(qq), mode(m), runnable(), cms(), openpgp(), wizard() { } EncryptEMailController::EncryptEMailController(const std::shared_ptr<ExecutionContext> &xc, Mode mode, QObject *p) : Controller(xc, p), d(new Private(mode, this)) { } EncryptEMailController::EncryptEMailController(Mode mode, QObject *p) : Controller(p), d(new Private(mode, this)) { } EncryptEMailController::~EncryptEMailController() { if (d->wizard && !d->wizard->isVisible()) { delete d->wizard; } //d->wizard->close(); ### ? } EncryptEMailController::Mode EncryptEMailController::mode() const { return d->mode; } void EncryptEMailController::setProtocol(Protocol proto) { d->ensureWizardCreated(); const Protocol protocol = d->wizard->presetProtocol(); kleo_assert(protocol == UnknownProtocol || protocol == proto); d->wizard->setPresetProtocol(proto); } Protocol EncryptEMailController::protocol() const { d->ensureWizardCreated(); return d->wizard->selectedProtocol(); } const char *EncryptEMailController::protocolAsString() const { switch (protocol()) { case OpenPGP: return "OpenPGP"; case CMS: return "CMS"; default: throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL), i18n("Call to EncryptEMailController::protocolAsString() is ambiguous.")); } } void EncryptEMailController::startResolveRecipients() { startResolveRecipients(std::vector<Mailbox>(), std::vector<Mailbox>()); } void EncryptEMailController::startResolveRecipients(const std::vector<Mailbox> &recipients, const std::vector<Mailbox> &senders) { d->ensureWizardCreated(); d->wizard->setRecipients(recipients, senders); d->ensureWizardVisible(); } void EncryptEMailController::Private::slotWizardRecipientsResolved() { Q_EMIT q->recipientsResolved(); } void EncryptEMailController::Private::slotWizardCanceled() { q->setLastError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel")); q->emitDoneOrError(); } void EncryptEMailController::setInputAndOutput(const std::shared_ptr<Input> &input, const std::shared_ptr<Output> &output) { setInputsAndOutputs(std::vector< std::shared_ptr<Input> >(1, input), std::vector< std::shared_ptr<Output> >(1, output)); } void EncryptEMailController::setInputsAndOutputs(const std::vector< std::shared_ptr<Input> > &inputs, const std::vector< std::shared_ptr<Output> > &outputs) { kleo_assert(!inputs.empty()); kleo_assert(outputs.size() == inputs.size()); std::vector< std::shared_ptr<EncryptEMailTask> > tasks; tasks.reserve(inputs.size()); d->ensureWizardCreated(); const std::vector<Key> keys = d->wizard->resolvedCertificates(); kleo_assert(!keys.empty()); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr<EncryptEMailTask> task(new EncryptEMailTask); task->setInput(inputs[i]); task->setOutput(outputs[i]); if (d->mode == ClipboardMode) { task->setAsciiArmor(true); } task->setRecipients(keys); tasks.push_back(task); } d->runnable.swap(tasks); } void EncryptEMailController::start() { std::shared_ptr<TaskCollection> coll(new TaskCollection); std::vector<std::shared_ptr<Task> > tmp; std::copy(d->runnable.begin(), d->runnable.end(), std::back_inserter(tmp)); coll->setTasks(tmp); d->ensureWizardCreated(); d->wizard->setTaskCollection(coll); - Q_FOREACH (const std::shared_ptr<Task> &t, tmp) { + for (const std::shared_ptr<Task> &t : qAsConst(tmp)) { connectTask(t); } d->schedule(); } void EncryptEMailController::Private::schedule() { if (!cms) if (const std::shared_ptr<EncryptEMailTask> t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr<EncryptEMailTask> t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (cms || openpgp) { return; } kleo_assert(runnable.empty()); q->emitDoneOrError(); } std::shared_ptr<EncryptEMailTask> EncryptEMailController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr<Kleo::Crypto::EncryptEMailTask> &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr<EncryptEMailTask>(); } const std::shared_ptr<EncryptEMailTask> result = *it; runnable.erase(it); return result; } void EncryptEMailController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result) { Q_UNUSED(result) Q_ASSERT(task); // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container if (task == d->cms.get()) { d->completed.push_back(d->cms); d->cms.reset(); } else if (task == d->openpgp.get()) { d->completed.push_back(d->openpgp); d->openpgp.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } void EncryptEMailController::cancel() { try { if (d->wizard) { d->wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void EncryptEMailController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. runnable.clear(); // a cancel() will result in a call to if (cms) { cms->cancel(); } if (openpgp) { openpgp->cancel(); } } void EncryptEMailController::Private::ensureWizardCreated() const { if (wizard) { return; } std::unique_ptr<EncryptEMailWizard> w(new EncryptEMailWizard); w->setAttribute(Qt::WA_DeleteOnClose); Kleo::EMailOperationsPreferences prefs; w->setQuickMode(prefs.quickEncryptEMail()); connect(w.get(), SIGNAL(recipientsResolved()), q, SLOT(slotWizardRecipientsResolved()), Qt::QueuedConnection); connect(w.get(), SIGNAL(canceled()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); wizard = w.release(); } void EncryptEMailController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(wizard); } #include "moc_encryptemailcontroller.cpp" diff --git a/src/crypto/gui/resolverecipientspage.cpp b/src/crypto/gui/resolverecipientspage.cpp index f0c04cb50..a76b8674e 100644 --- a/src/crypto/gui/resolverecipientspage.cpp +++ b/src/crypto/gui/resolverecipientspage.cpp @@ -1,704 +1,704 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/resolverecipientspage.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 <config-kleopatra.h> #include "resolverecipientspage.h" #include "resolverecipientspage_p.h" #include <dialogs/certificateselectiondialog.h> #include <crypto/certificateresolver.h> #include <Libkleo/KeyCache> #include <Libkleo/Formatting> #include <kmime/kmime_header_parsing.h> #include <gpgme++/key.h> #include <KLocalizedString> #include <QButtonGroup> #include <QComboBox> #include <QHBoxLayout> #include <QLabel> #include <QListWidget> #include <QPointer> #include <QPushButton> #include <QRadioButton> #include <QToolButton> #include <QStringList> #include <QVBoxLayout> using namespace GpgME; using namespace Kleo; using namespace Kleo::Dialogs; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace KMime::Types; ResolveRecipientsPage::ListWidget::ListWidget(QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), m_protocol(UnknownProtocol) { m_listWidget = new QListWidget; m_listWidget->setSelectionMode(QAbstractItemView::MultiSelection); QVBoxLayout *const layout = new QVBoxLayout(this); layout->addWidget(m_listWidget); connect(m_listWidget, &QListWidget::itemSelectionChanged, this, &ListWidget::onSelectionChange); } ResolveRecipientsPage::ListWidget::~ListWidget() { } void ResolveRecipientsPage::ListWidget::onSelectionChange() { const auto widgetskeys = widgets.keys(); for (const QString &i : widgetskeys) { Q_ASSERT(items.contains(i)); widgets[i]->setSelected(items[i]->isSelected()); } Q_EMIT selectionChanged(); } void ResolveRecipientsPage::ListWidget::addEntry(const Mailbox &mbox) { addEntry(mbox.prettyAddress(), mbox.prettyAddress(), mbox); } void ResolveRecipientsPage::ListWidget::addEntry(const QString &id, const QString &name) { addEntry(id, name, Mailbox()); } void ResolveRecipientsPage::ListWidget::addEntry(const QString &id, const QString &name, const Mailbox &mbox) { Q_ASSERT(!widgets.contains(id) && !items.contains(id)); QListWidgetItem *item = new QListWidgetItem; item->setData(IdRole, id); ItemWidget *wid = new ItemWidget(id, name, mbox, this); connect(wid, &ItemWidget::changed, this, &ListWidget::completeChanged); wid->setProtocol(m_protocol); item->setSizeHint(wid->sizeHint()); m_listWidget->addItem(item); m_listWidget->setItemWidget(item, wid); widgets[id] = wid; items[id] = item; } Mailbox ResolveRecipientsPage::ListWidget::mailbox(const QString &id) const { return widgets.contains(id) ? widgets[id]->mailbox() : Mailbox(); } void ResolveRecipientsPage::ListWidget::setCertificates(const QString &id, const std::vector<Key> &pgp, const std::vector<Key> &cms) { Q_ASSERT(widgets.contains(id)); widgets[id]->setCertificates(pgp, cms); } Key ResolveRecipientsPage::ListWidget::selectedCertificate(const QString &id) const { return widgets.contains(id) ? widgets[id]->selectedCertificate() : Key(); } GpgME::Key ResolveRecipientsPage::ListWidget::selectedCertificate(const QString &id, GpgME::Protocol prot) const { return widgets.contains(id) ? widgets[id]->selectedCertificate(prot) : Key(); } QStringList ResolveRecipientsPage::ListWidget::identifiers() const { return widgets.keys(); } void ResolveRecipientsPage::ListWidget::setProtocol(GpgME::Protocol prot) { if (m_protocol == prot) { return; } m_protocol = prot; - Q_FOREACH (ItemWidget *i, widgets) { + for (ItemWidget *i : qAsConst(widgets)) { i->setProtocol(prot); } } void ResolveRecipientsPage::ListWidget::removeEntry(const QString &id) { if (!widgets.contains(id)) { return; } delete items[id]; items.remove(id); delete widgets[id]; widgets.remove(id); } void ResolveRecipientsPage::ListWidget::showSelectionDialog(const QString &id) { if (!widgets.contains(id)) { return; } widgets[id]->showSelectionDialog(); } QStringList ResolveRecipientsPage::ListWidget::selectedEntries() const { QStringList entries; const QList<QListWidgetItem *> items = m_listWidget->selectedItems(); entries.reserve(items.count()); for (const QListWidgetItem *i : items) { entries.append(i->data(IdRole).toString()); } return entries; } ResolveRecipientsPage::ItemWidget::ItemWidget(const QString &id, const QString &name, const Mailbox &mbox, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), m_id(id), m_mailbox(mbox), m_protocol(UnknownProtocol), m_selected(false) { Q_ASSERT(!m_id.isEmpty()); setAutoFillBackground(true); QHBoxLayout *layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addSpacing(15); m_nameLabel = new QLabel; m_nameLabel->setText(name); layout->addWidget(m_nameLabel); layout->addStretch(); m_certLabel = new QLabel; m_certLabel->setText(i18n("<i>No certificate selected</i>")); layout->addWidget(m_certLabel); m_certCombo = new QComboBox; connect(m_certCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(changed())); layout->addWidget(m_certCombo); m_selectButton = new QToolButton; m_selectButton->setText(i18n("...")); connect(m_selectButton, &QAbstractButton::clicked, this, &ItemWidget::showSelectionDialog); layout->addWidget(m_selectButton); layout->addSpacing(15); setCertificates(std::vector<Key>(), std::vector<Key>()); } void ResolveRecipientsPage::ItemWidget::updateVisibility() { m_certLabel->setVisible(m_certCombo->count() == 0); m_certCombo->setVisible(m_certCombo->count() > 0); } ResolveRecipientsPage::ItemWidget::~ItemWidget() { } QString ResolveRecipientsPage::ItemWidget::id() const { return m_id; } void ResolveRecipientsPage::ItemWidget::setSelected(bool selected) { if (m_selected == selected) { return; } m_selected = selected; setBackgroundRole(selected ? QPalette::Highlight : QPalette::Base); const QPalette::ColorRole foreground = selected ? QPalette::HighlightedText : QPalette::Text; setForegroundRole(foreground); m_nameLabel->setForegroundRole(foreground); m_certLabel->setForegroundRole(foreground); } bool ResolveRecipientsPage::ItemWidget::isSelected() const { return m_selected; } static CertificateSelectionDialog::Option protocol2option(GpgME::Protocol proto) { switch (proto) { case OpenPGP: return CertificateSelectionDialog::OpenPGPFormat; case CMS: return CertificateSelectionDialog::CMSFormat; default: return CertificateSelectionDialog::AnyFormat; } } static CertificateSelectionDialog *createCertificateSelectionDialog(QWidget *parent, GpgME::Protocol prot) { CertificateSelectionDialog *const dlg = new CertificateSelectionDialog(parent); const CertificateSelectionDialog::Options options = CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::EncryptOnly | CertificateSelectionDialog::MultiSelection | protocol2option(prot); dlg->setOptions(options); return dlg; } void ResolveRecipientsPage::ItemWidget::showSelectionDialog() { QPointer<CertificateSelectionDialog> dlg = createCertificateSelectionDialog(this, m_protocol); if (dlg->exec() == QDialog::Accepted && dlg /* still with us? */) { const GpgME::Key cert = dlg->selectedCertificate(); if (!cert.isNull()) { addCertificateToComboBox(cert); selectCertificateInComboBox(cert); } } delete dlg; } Mailbox ResolveRecipientsPage::ItemWidget::mailbox() const { return m_mailbox; } void ResolveRecipientsPage::ItemWidget::selectCertificateInComboBox(const Key &key) { m_certCombo->setCurrentIndex(m_certCombo->findData(QLatin1String(key.keyID()))); } void ResolveRecipientsPage::ItemWidget::addCertificateToComboBox(const GpgME::Key &key) { m_certCombo->addItem(Formatting::formatForComboBox(key), QByteArray(key.keyID())); if (m_certCombo->count() == 1) { m_certCombo->setCurrentIndex(0); } updateVisibility(); } void ResolveRecipientsPage::ItemWidget::resetCertificates() { std::vector<Key> certs; Key selected; switch (m_protocol) { case OpenPGP: certs = m_pgp; break; case CMS: certs = m_cms; break; case UnknownProtocol: certs = m_cms; certs.insert(certs.end(), m_pgp.begin(), m_pgp.end()); } m_certCombo->clear(); - Q_FOREACH (const Key &i, certs) { + for (const Key &i : qAsConst(certs)) { addCertificateToComboBox(i); } if (!m_selectedCertificates[m_protocol].isNull()) { selectCertificateInComboBox(m_selectedCertificates[m_protocol]); } else if (m_certCombo->count() > 0) { m_certCombo->setCurrentIndex(0); } updateVisibility(); Q_EMIT changed(); } void ResolveRecipientsPage::ItemWidget::setProtocol(Protocol prot) { if (m_protocol == prot) { return; } m_selectedCertificates[m_protocol] = selectedCertificate(); if (m_protocol != UnknownProtocol) { (m_protocol == OpenPGP ? m_pgp : m_cms) = certificates(); } m_protocol = prot; resetCertificates(); } void ResolveRecipientsPage::ItemWidget::setCertificates(const std::vector<Key> &pgp, const std::vector<Key> &cms) { m_pgp = pgp; m_cms = cms; resetCertificates(); } Key ResolveRecipientsPage::ItemWidget::selectedCertificate() const { return KeyCache::instance()->findByKeyIDOrFingerprint(m_certCombo->itemData(m_certCombo->currentIndex(), ListWidget::IdRole).toString().toStdString()); } GpgME::Key ResolveRecipientsPage::ItemWidget::selectedCertificate(GpgME::Protocol prot) const { return prot == m_protocol ? selectedCertificate() : m_selectedCertificates.value(prot); } std::vector<Key> ResolveRecipientsPage::ItemWidget::certificates() const { std::vector<Key> certs; for (int i = 0; i < m_certCombo->count(); ++i) { certs.push_back(KeyCache::instance()->findByKeyIDOrFingerprint(m_certCombo->itemData(i, ListWidget::IdRole).toString().toStdString())); } return certs; } class ResolveRecipientsPage::Private { friend class ::Kleo::Crypto::Gui::ResolveRecipientsPage; ResolveRecipientsPage *const q; public: explicit Private(ResolveRecipientsPage *qq); ~Private(); void setSelectedProtocol(Protocol protocol); void selectionChanged(); void removeSelectedEntries(); void addRecipient(); void addRecipient(const Mailbox &mbox); void addRecipient(const QString &id, const QString &name); void updateProtocolRBVisibility(); void protocolSelected(int prot); void writeSelectedCertificatesToPreferences(); void completeChangedInternal(); private: ListWidget *m_listWidget; QPushButton *m_addButton; QPushButton *m_removeButton; QRadioButton *m_pgpRB; QRadioButton *m_cmsRB; QLabel *m_additionalRecipientsLabel; Protocol m_presetProtocol; Protocol m_selectedProtocol; bool m_multipleProtocolsAllowed; std::shared_ptr<RecipientPreferences> m_recipientPreferences; }; ResolveRecipientsPage::Private::Private(ResolveRecipientsPage *qq) : q(qq), m_presetProtocol(UnknownProtocol), m_selectedProtocol(m_presetProtocol), m_multipleProtocolsAllowed(false), m_recipientPreferences() { connect(q, SIGNAL(completeChanged()), q, SLOT(completeChangedInternal())); q->setTitle(i18n("<b>Recipients</b>")); QVBoxLayout *const layout = new QVBoxLayout(q); m_listWidget = new ListWidget; connect(m_listWidget, SIGNAL(selectionChanged()), q, SLOT(selectionChanged())); connect(m_listWidget, &ListWidget::completeChanged, q, &WizardPage::completeChanged); layout->addWidget(m_listWidget); m_additionalRecipientsLabel = new QLabel; m_additionalRecipientsLabel->setWordWrap(true); layout->addWidget(m_additionalRecipientsLabel); m_additionalRecipientsLabel->setVisible(false); QWidget *buttonWidget = new QWidget; QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget); buttonLayout->setContentsMargins(0, 0, 0, 0); m_addButton = new QPushButton; connect(m_addButton, SIGNAL(clicked()), q, SLOT(addRecipient())); m_addButton->setText(i18n("Add Recipient...")); buttonLayout->addWidget(m_addButton); m_removeButton = new QPushButton; m_removeButton->setEnabled(false); m_removeButton->setText(i18n("Remove Selected")); connect(m_removeButton, SIGNAL(clicked()), q, SLOT(removeSelectedEntries())); buttonLayout->addWidget(m_removeButton); buttonLayout->addStretch(); layout->addWidget(buttonWidget); QWidget *protocolWidget = new QWidget; QHBoxLayout *protocolLayout = new QHBoxLayout(protocolWidget); QButtonGroup *protocolGroup = new QButtonGroup(q); connect(protocolGroup, SIGNAL(buttonClicked(int)), q, SLOT(protocolSelected(int))); m_pgpRB = new QRadioButton; m_pgpRB->setText(i18n("OpenPGP")); protocolGroup->addButton(m_pgpRB, OpenPGP); protocolLayout->addWidget(m_pgpRB); m_cmsRB = new QRadioButton; m_cmsRB->setText(i18n("S/MIME")); protocolGroup->addButton(m_cmsRB, CMS); protocolLayout->addWidget(m_cmsRB); protocolLayout->addStretch(); layout->addWidget(protocolWidget); } ResolveRecipientsPage::Private::~Private() {} void ResolveRecipientsPage::Private::completeChangedInternal() { const bool isComplete = q->isComplete(); const std::vector<Key> keys = q->resolvedCertificates(); const bool haveSecret = std::find_if(keys.begin(), keys.end(), [](const Key &key) { return key.hasSecret(); }) != keys.end(); if (isComplete && !haveSecret) { q->setExplanation(i18n("<b>Warning:</b> None of the selected certificates seem to be your own. You will not be able to decrypt the encrypted data again.")); } else { q->setExplanation(QString()); } } void ResolveRecipientsPage::Private::updateProtocolRBVisibility() { const bool visible = !m_multipleProtocolsAllowed && m_presetProtocol == UnknownProtocol; m_cmsRB->setVisible(visible); m_pgpRB->setVisible(visible); if (visible) { if (m_selectedProtocol == CMS) { m_cmsRB->click(); } else { m_pgpRB->click(); } } } bool ResolveRecipientsPage::isComplete() const { const QStringList ids = d->m_listWidget->identifiers(); if (ids.isEmpty()) { return false; } for (const QString &i : ids) { if (d->m_listWidget->selectedCertificate(i).isNull()) { return false; } } return true; } ResolveRecipientsPage::ResolveRecipientsPage(QWidget *parent) : WizardPage(parent), d(new Private(this)) { } ResolveRecipientsPage::~ResolveRecipientsPage() {} Protocol ResolveRecipientsPage::selectedProtocol() const { return d->m_selectedProtocol; } void ResolveRecipientsPage::Private::setSelectedProtocol(Protocol protocol) { if (m_selectedProtocol == protocol) { return; } m_selectedProtocol = protocol; m_listWidget->setProtocol(m_selectedProtocol); Q_EMIT q->selectedProtocolChanged(); } void ResolveRecipientsPage::Private::protocolSelected(int p) { const Protocol protocol = static_cast<Protocol>(p); Q_ASSERT(protocol != UnknownProtocol); setSelectedProtocol(protocol); } void ResolveRecipientsPage::setPresetProtocol(Protocol prot) { if (d->m_presetProtocol == prot) { return; } d->m_presetProtocol = prot; d->setSelectedProtocol(prot); if (prot != UnknownProtocol) { d->m_multipleProtocolsAllowed = false; } d->updateProtocolRBVisibility(); } Protocol ResolveRecipientsPage::presetProtocol() const { return d->m_presetProtocol; } bool ResolveRecipientsPage::multipleProtocolsAllowed() const { return d->m_multipleProtocolsAllowed; } void ResolveRecipientsPage::setMultipleProtocolsAllowed(bool allowed) { if (d->m_multipleProtocolsAllowed == allowed) { return; } d->m_multipleProtocolsAllowed = allowed; if (d->m_multipleProtocolsAllowed) { setPresetProtocol(UnknownProtocol); d->setSelectedProtocol(UnknownProtocol); } d->updateProtocolRBVisibility(); } void ResolveRecipientsPage::Private::addRecipient(const QString &id, const QString &name) { m_listWidget->addEntry(id, name); } void ResolveRecipientsPage::Private::addRecipient(const Mailbox &mbox) { m_listWidget->addEntry(mbox); } void ResolveRecipientsPage::Private::addRecipient() { QPointer<CertificateSelectionDialog> dlg = createCertificateSelectionDialog(q, q->selectedProtocol()); if (dlg->exec() != QDialog::Accepted || !dlg /*q already deleted*/) { return; } const std::vector<Key> keys = dlg->selectedCertificates(); int i = 0; - Q_FOREACH (const Key &key, keys) { + for (const Key &key : keys) { const QStringList existing = m_listWidget->identifiers(); QString rec = i18n("Recipient"); while (existing.contains(rec)) { rec = i18nc("%1 == number", "Recipient (%1)", ++i); } addRecipient(rec, rec); const std::vector<Key> pgp = key.protocol() == OpenPGP ? std::vector<Key>(1, key) : std::vector<Key>(); const std::vector<Key> cms = key.protocol() == CMS ? std::vector<Key>(1, key) : std::vector<Key>(); m_listWidget->setCertificates(rec, pgp, cms); } Q_EMIT q->completeChanged(); } namespace { std::vector<Key> makeSuggestions(const std::shared_ptr<RecipientPreferences> &prefs, const Mailbox &mb, GpgME::Protocol prot) { std::vector<Key> suggestions; const Key remembered = prefs ? prefs->preferredCertificate(mb, prot) : Key(); if (!remembered.isNull()) { suggestions.push_back(remembered); } else { suggestions = CertificateResolver::resolveRecipient(mb, prot); } return suggestions; } } static QString listKeysForInfo(const std::vector<Key> &keys) { QStringList list; std::transform(keys.begin(), keys.end(), list.begin(), &Formatting::formatKeyLink); return list.join(QLatin1String("<br/>")); } void ResolveRecipientsPage::setAdditionalRecipientsInfo(const std::vector<Key> &recipients) { d->m_additionalRecipientsLabel->setVisible(!recipients.empty()); if (recipients.empty()) { return; } d->m_additionalRecipientsLabel->setText( i18n("<qt><p>Recipients predefined via GnuPG settings:</p>%1</qt>", listKeysForInfo(recipients))); } void ResolveRecipientsPage::setRecipients(const std::vector<Mailbox> &recipients, const std::vector<Mailbox> &encryptToSelfRecipients) { uint cmsCount = 0; uint pgpCount = 0; uint senders = 0; - Q_FOREACH (const Mailbox &mb, encryptToSelfRecipients) { + for (const Mailbox &mb : encryptToSelfRecipients) { const QString id = QLatin1String("sender-") + QString::number(++senders); d->m_listWidget->addEntry(id, i18n("Sender"), mb); const std::vector<Key> pgp = makeSuggestions(d->m_recipientPreferences, mb, OpenPGP); const std::vector<Key> cms = makeSuggestions(d->m_recipientPreferences, mb, CMS); pgpCount += !pgp.empty(); cmsCount += !cms.empty(); d->m_listWidget->setCertificates(id, pgp, cms); } - Q_FOREACH (const Mailbox &i, recipients) { + for (const Mailbox &i : recipients) { //TODO: const QString address = i.prettyAddress(); d->addRecipient(i); const std::vector<Key> pgp = makeSuggestions(d->m_recipientPreferences, i, OpenPGP); const std::vector<Key> cms = makeSuggestions(d->m_recipientPreferences, i, CMS); pgpCount += pgp.empty() ? 0 : 1; cmsCount += cms.empty() ? 0 : 1; d->m_listWidget->setCertificates(address, pgp, cms); } if (d->m_presetProtocol == UnknownProtocol && !d->m_multipleProtocolsAllowed) { (cmsCount > pgpCount ? d->m_cmsRB : d->m_pgpRB)->click(); } } std::vector<Key> ResolveRecipientsPage::resolvedCertificates() const { std::vector<Key> certs; Q_FOREACH (const QString &i, d->m_listWidget->identifiers()) { const GpgME::Key cert = d->m_listWidget->selectedCertificate(i); if (!cert.isNull()) { certs.push_back(cert); } } return certs; } void ResolveRecipientsPage::Private::selectionChanged() { m_removeButton->setEnabled(!m_listWidget->selectedEntries().isEmpty()); } void ResolveRecipientsPage::Private::removeSelectedEntries() { Q_FOREACH (const QString &i, m_listWidget->selectedEntries()) { m_listWidget->removeEntry(i); } Q_EMIT q->completeChanged(); } void ResolveRecipientsPage::setRecipientsUserMutable(bool isMutable) { d->m_addButton->setVisible(isMutable); d->m_removeButton->setVisible(isMutable); } bool ResolveRecipientsPage::recipientsUserMutable() const { return d->m_addButton->isVisible(); } std::shared_ptr<RecipientPreferences> ResolveRecipientsPage::recipientPreferences() const { return d->m_recipientPreferences; } void ResolveRecipientsPage::setRecipientPreferences(const std::shared_ptr<RecipientPreferences> &prefs) { d->m_recipientPreferences = prefs; } void ResolveRecipientsPage::Private::writeSelectedCertificatesToPreferences() { if (!m_recipientPreferences) { return; } Q_FOREACH (const QString &i, m_listWidget->identifiers()) { const Mailbox mbox = m_listWidget->mailbox(i); if (!mbox.hasAddress()) { continue; } const Key pgp = m_listWidget->selectedCertificate(i, OpenPGP); if (!pgp.isNull()) { m_recipientPreferences->setPreferredCertificate(mbox, OpenPGP, pgp); } const Key cms = m_listWidget->selectedCertificate(i, CMS); if (!cms.isNull()) { m_recipientPreferences->setPreferredCertificate(mbox, CMS, cms); } } } void ResolveRecipientsPage::onNext() { d->writeSelectedCertificatesToPreferences(); } #include "moc_resolverecipientspage_p.cpp" #include "moc_resolverecipientspage.cpp" diff --git a/src/crypto/gui/signencryptemailconflictdialog.cpp b/src/crypto/gui/signencryptemailconflictdialog.cpp index 2a2eb9816..e32b758f6 100644 --- a/src/crypto/gui/signencryptemailconflictdialog.cpp +++ b/src/crypto/gui/signencryptemailconflictdialog.cpp @@ -1,644 +1,644 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signencryptemailconflictdialog.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 <config-kleopatra.h> #include "signencryptemailconflictdialog.h" #include <crypto/sender.h> #include <crypto/recipient.h> #include "dialogs/certificateselectiondialog.h" #include "certificateselectionline.h" #include <Libkleo/GnuPG> #include "utils/gui-helper.h" #include "utils/kleo_assert.h" #include <Libkleo/Stl_Util> #include <Libkleo/Formatting> #include <gpgme++/key.h> #include <kmime/kmime_header_parsing.h> #include <KLocalizedString> #include <KColorScheme> #include <QLabel> #include <QLayout> #include <QGroupBox> #include <QDialogButtonBox> #include <QCheckBox> #include <QRadioButton> #include <QPushButton> #include <QPointer> #include <QSignalBlocker> #include <QToolButton> #include <algorithm> #include <iterator> using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace Kleo::Dialogs; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) Q_DECLARE_METATYPE(GpgME::UserID) static CertificateSelectionDialog * create_certificate_selection_dialog(QWidget *parent, Protocol proto) { CertificateSelectionDialog *const dlg = new CertificateSelectionDialog(parent); dlg->setOptions(proto == OpenPGP ? CertificateSelectionDialog::OpenPGPFormat : proto == CMS ? CertificateSelectionDialog::CMSFormat : CertificateSelectionDialog::AnyFormat); return dlg; } static CertificateSelectionDialog * create_encryption_certificate_selection_dialog(QWidget *parent, Protocol proto, const QString &mailbox) { CertificateSelectionDialog *const dlg = create_certificate_selection_dialog(parent, proto); dlg->setCustomLabelText(i18n("Please select an encryption certificate for recipient \"%1\"", mailbox)); dlg->setOptions(CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::EncryptOnly | dlg->options()); return dlg; } static CertificateSelectionDialog * create_signing_certificate_selection_dialog(QWidget *parent, Protocol proto, const QString &mailbox) { CertificateSelectionDialog *const dlg = create_certificate_selection_dialog(parent, proto); dlg->setCustomLabelText(i18n("Please select a signing certificate for sender \"%1\"", mailbox)); dlg->setOptions(CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::SignOnly | CertificateSelectionDialog::SecretKeys | dlg->options()); return dlg; } static QString make_top_label_conflict_text(bool sign, bool enc) { return sign && enc ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for all recipients/senders of the message.\n" "Please select the correct certificates for each recipient:") : sign ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for the sender of the message.\n" "Please select the correct certificates for the sender:") : enc ? i18n("Kleopatra cannot unambiguously determine matching certificates " "for all recipients of the message.\n" "Please select the correct certificates for each recipient:") : /* else */ (kleo_assert_fail(sign || enc), QString()); } static QString make_top_label_quickmode_text(bool sign, bool enc) { return enc ? i18n("Please verify that correct certificates have been selected for each recipient:") : sign ? i18n("Please verify that the correct certificate has been selected for the sender:") : /*else*/ (kleo_assert_fail(sign || enc), QString()); } class SignEncryptEMailConflictDialog::Private { friend class ::Kleo::Crypto::Gui::SignEncryptEMailConflictDialog; SignEncryptEMailConflictDialog *const q; public: explicit Private(SignEncryptEMailConflictDialog *qq) : q(qq), senders(), recipients(), sign(true), encrypt(true), presetProtocol(UnknownProtocol), ui(q) { } private: void updateTopLabelText() { ui.conflictTopLB.setText(make_top_label_conflict_text(sign, encrypt)); ui.quickModeTopLB.setText(make_top_label_quickmode_text(sign, encrypt)); } void showHideWidgets() { const Protocol proto = q->selectedProtocol(); const bool quickMode = q->isQuickMode(); const bool needProtocolSelection = presetProtocol == UnknownProtocol; const bool needShowAllRecipientsCB = quickMode ? false : needProtocolSelection ? needShowAllRecipients(OpenPGP) || needShowAllRecipients(CMS) : /* else */ needShowAllRecipients(proto) ; ui.showAllRecipientsCB.setVisible(needShowAllRecipientsCB); ui.pgpRB.setVisible(needProtocolSelection); ui.cmsRB.setVisible(needProtocolSelection); const bool showAll = !needShowAllRecipientsCB || ui.showAllRecipientsCB.isChecked(); bool first; first = true; - Q_FOREACH (const CertificateSelectionLine &line, ui.signers) { + for (const CertificateSelectionLine &line : qAsConst(ui.signers)) { line.showHide(proto, first, showAll, sign); } ui.selectSigningCertificatesGB.setVisible(sign && (showAll || !first)); first = true; - Q_FOREACH (const CertificateSelectionLine &line, ui.recipients) { + for (const CertificateSelectionLine &line : qAsConst(ui.recipients)) { line.showHide(proto, first, showAll, encrypt); } ui.selectEncryptionCertificatesGB.setVisible(encrypt && (showAll || !first)); } bool needShowAllRecipients(Protocol proto) const { if (sign) { if (const unsigned int num = std::count_if(ui.signers.cbegin(), ui.signers.cend(), [proto](const CertificateSelectionLine &l) { return l.wasInitiallyAmbiguous(proto); })) { if (num != ui.signers.size()) { return true; } } } if (encrypt) { if (const unsigned int num = std::count_if(ui.recipients.cbegin(), ui.recipients.cend(), [proto](const CertificateSelectionLine &l) { return l.wasInitiallyAmbiguous(proto); })) { if (num != ui.recipients.size()) { return true; } } } return false; } void createSendersAndRecipients() { ui.clearSendersAndRecipients(); ui.addSelectSigningCertificatesGB(); for (const Sender &s : qAsConst(senders)) { addSigner(s); } ui.addSelectEncryptionCertificatesGB(); for (const Sender &s : qAsConst(senders)) { addRecipient(s); } for (const Recipient &r : qAsConst(recipients)) { addRecipient(r); } } void addSigner(const Sender &s) { ui.addSigner(s.mailbox().prettyAddress(), s.signingCertificateCandidates(OpenPGP), s.isSigningAmbiguous(OpenPGP), s.signingCertificateCandidates(CMS), s.isSigningAmbiguous(CMS), q); } void addRecipient(const Sender &s) { ui.addRecipient(s.mailbox().prettyAddress(), s.encryptToSelfCertificateCandidates(OpenPGP), s.isEncryptionAmbiguous(OpenPGP), s.encryptToSelfCertificateCandidates(CMS), s.isEncryptionAmbiguous(CMS), q); } void addRecipient(const Recipient &r) { ui.addRecipient(r.mailbox().prettyAddress(), r.encryptionCertificateCandidates(OpenPGP), r.isEncryptionAmbiguous(OpenPGP), r.encryptionCertificateCandidates(CMS), r.isEncryptionAmbiguous(CMS), q); } bool isComplete(Protocol proto) const; private: void updateComplianceStatus() { if (q->selectedProtocol() == UnknownProtocol || (q->resolvedSigningKeys().empty() && q->resolvedEncryptionKeys().empty())) { return; } // Handle compliance bool de_vs = true; for (const auto &key: q->resolvedSigningKeys()) { if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } if (de_vs) { for (const auto &key: q->resolvedEncryptionKeys()) { if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { de_vs = false; break; } } } auto btn = ui.buttonBox.button(QDialogButtonBox::Ok); btn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); btn->setStyleSheet(QStringLiteral("background-color: ") + (de_vs ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); ui.complianceLB.setText(de_vs ? 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()) : 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())); ui.complianceLB.setVisible(true); } void updateDialogStatus() { ui.setOkButtonEnabled(q->isComplete()); if (Kleo::gpgComplianceP("de-vs")) { updateComplianceStatus(); } } void slotCompleteChanged() { updateDialogStatus(); } void slotShowAllRecipientsToggled(bool) { showHideWidgets(); } void slotProtocolChanged() { showHideWidgets(); updateDialogStatus(); } void slotCertificateSelectionDialogRequested() { const QObject *const s = q->sender(); const Protocol proto = q->selectedProtocol(); QPointer<CertificateSelectionDialog> dlg; Q_FOREACH (const CertificateSelectionLine &l, ui.signers) if (s == l.toolButton()) { dlg = create_signing_certificate_selection_dialog(q, proto, l.mailboxText()); if (dlg->exec()) { l.addAndSelectCertificate(dlg->selectedCertificate()); } // ### switch to key.protocol(), in case proto == UnknownProtocol break; } Q_FOREACH (const CertificateSelectionLine &l, ui.recipients) if (s == l.toolButton()) { dlg = create_encryption_certificate_selection_dialog(q, proto, l.mailboxText()); if (dlg->exec()) { l.addAndSelectCertificate(dlg->selectedCertificate()); } // ### switch to key.protocol(), in case proto == UnknownProtocol break; } #ifndef Q_OS_WIN // This leads to a crash on Windows. We don't really // leak memory here anyway because the destruction of the // dialog happens when the parent (q) is destroyed anyway. delete dlg; #endif } private: std::vector<Sender> senders; std::vector<Recipient> recipients; bool sign : 1; bool encrypt : 1; Protocol presetProtocol; private: struct Ui { QLabel conflictTopLB, quickModeTopLB; QCheckBox showAllRecipientsCB; QRadioButton pgpRB, cmsRB; QGroupBox selectSigningCertificatesGB; QGroupBox selectEncryptionCertificatesGB; QCheckBox quickModeCB; QDialogButtonBox buttonBox; QVBoxLayout vlay; QHBoxLayout hlay; QHBoxLayout hlay2; QGridLayout glay; std::vector<CertificateSelectionLine> signers, recipients; QLabel complianceLB; void setOkButtonEnabled(bool enable) { return buttonBox.button(QDialogButtonBox::Ok)->setEnabled(enable); } explicit Ui(SignEncryptEMailConflictDialog *q) : conflictTopLB(make_top_label_conflict_text(true, true), q), quickModeTopLB(make_top_label_quickmode_text(true, true), q), showAllRecipientsCB(i18n("Show all recipients"), q), pgpRB(i18n("OpenPGP"), q), cmsRB(i18n("S/MIME"), q), selectSigningCertificatesGB(i18n("Select Signing Certificate"), q), selectEncryptionCertificatesGB(i18n("Select Encryption Certificate"), q), quickModeCB(i18n("Only show this dialog in case of conflicts (experimental)"), q), buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, q), vlay(q), hlay(), glay(), signers(), recipients() { KDAB_SET_OBJECT_NAME(conflictTopLB); KDAB_SET_OBJECT_NAME(quickModeTopLB); KDAB_SET_OBJECT_NAME(showAllRecipientsCB); KDAB_SET_OBJECT_NAME(pgpRB); KDAB_SET_OBJECT_NAME(cmsRB); KDAB_SET_OBJECT_NAME(selectSigningCertificatesGB); KDAB_SET_OBJECT_NAME(selectEncryptionCertificatesGB); KDAB_SET_OBJECT_NAME(quickModeCB); KDAB_SET_OBJECT_NAME(buttonBox); KDAB_SET_OBJECT_NAME(hlay); KDAB_SET_OBJECT_NAME(glay); KDAB_SET_OBJECT_NAME(vlay); q->setWindowTitle(i18nc("@title:window", "Select Certificates for Message")); conflictTopLB.hide(); selectSigningCertificatesGB.setFlat(true); selectEncryptionCertificatesGB.setFlat(true); selectSigningCertificatesGB.setAlignment(Qt::AlignCenter); selectEncryptionCertificatesGB.setAlignment(Qt::AlignCenter); glay.setColumnStretch(2, 1); glay.setColumnStretch(3, 1); vlay.setSizeConstraint(QLayout::SetMinimumSize); vlay.addWidget(&conflictTopLB); vlay.addWidget(&quickModeTopLB); hlay.addWidget(&showAllRecipientsCB); hlay.addStretch(1); hlay.addWidget(&pgpRB); hlay.addWidget(&cmsRB); vlay.addLayout(&hlay); addSelectSigningCertificatesGB(); addSelectEncryptionCertificatesGB(); vlay.addLayout(&glay); vlay.addStretch(1); complianceLB.setVisible(false); hlay2.addStretch(1); hlay2.addWidget(&complianceLB, 0, Qt::AlignRight); hlay2.addWidget(&buttonBox, 0, Qt::AlignRight); vlay.addWidget(&quickModeCB, 0, Qt::AlignRight); vlay.addLayout(&hlay2); connect(&buttonBox, &QDialogButtonBox::accepted, q, &SignEncryptEMailConflictDialog::accept); connect(&buttonBox, &QDialogButtonBox::rejected, q, &SignEncryptEMailConflictDialog::reject); connect(&showAllRecipientsCB, SIGNAL(toggled(bool)), q, SLOT(slotShowAllRecipientsToggled(bool))); connect(&pgpRB, SIGNAL(toggled(bool)), q, SLOT(slotProtocolChanged())); connect(&cmsRB, SIGNAL(toggled(bool)), q, SLOT(slotProtocolChanged())); } void clearSendersAndRecipients() { std::vector<CertificateSelectionLine> sig, enc; sig.swap(signers); enc.swap(recipients); std::for_each(sig.begin(), sig.end(), std::mem_fn(&CertificateSelectionLine::kill)); std::for_each(enc.begin(), enc.end(), std::mem_fn(&CertificateSelectionLine::kill)); glay.removeWidget(&selectSigningCertificatesGB); glay.removeWidget(&selectEncryptionCertificatesGB); } void addSelectSigningCertificatesGB() { glay.addWidget(&selectSigningCertificatesGB, glay.rowCount(), 0, 1, CertificateSelectionLine::NumColumns); } void addSelectEncryptionCertificatesGB() { glay.addWidget(&selectEncryptionCertificatesGB, glay.rowCount(), 0, 1, CertificateSelectionLine::NumColumns); } void addSigner(const QString &mailbox, const std::vector<Key> &pgp, bool pgpAmbiguous, const std::vector<Key> &cms, bool cmsAmbiguous, QWidget *q) { CertificateSelectionLine line(i18n("From:"), mailbox, pgp, pgpAmbiguous, cms, cmsAmbiguous, q, glay); signers.push_back(line); } void addRecipient(const QString &mailbox, const std::vector<Key> &pgp, bool pgpAmbiguous, const std::vector<Key> &cms, bool cmsAmbiguous, QWidget *q) { CertificateSelectionLine line(i18n("To:"), mailbox, pgp, pgpAmbiguous, cms, cmsAmbiguous, q, glay); recipients.push_back(line); } } ui; }; SignEncryptEMailConflictDialog::SignEncryptEMailConflictDialog(QWidget *parent) : QDialog(parent), d(new Private(this)) { } SignEncryptEMailConflictDialog::~SignEncryptEMailConflictDialog() {} void SignEncryptEMailConflictDialog::setPresetProtocol(Protocol p) { if (p == d->presetProtocol) { return; } const QSignalBlocker pgpBlocker(d->ui.pgpRB); const QSignalBlocker cmsBlocker(d->ui.cmsRB); really_check(d->ui.pgpRB, p == OpenPGP); really_check(d->ui.cmsRB, p == CMS); d->presetProtocol = p; d->showHideWidgets(); d->updateDialogStatus(); } Protocol SignEncryptEMailConflictDialog::selectedProtocol() const { if (d->presetProtocol != UnknownProtocol) { return d->presetProtocol; } if (d->ui.pgpRB.isChecked()) { return OpenPGP; } if (d->ui.cmsRB.isChecked()) { return CMS; } return UnknownProtocol; } void SignEncryptEMailConflictDialog::setSubject(const QString &subject) { setWindowTitle(i18nc("@title:window", "Select Certificates for Message \"%1\"", subject)); } void SignEncryptEMailConflictDialog::setSign(bool sign) { if (sign == d->sign) { return; } d->sign = sign; d->updateTopLabelText(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setEncrypt(bool encrypt) { if (encrypt == d->encrypt) { return; } d->encrypt = encrypt; d->updateTopLabelText(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setSenders(const std::vector<Sender> &senders) { if (senders == d->senders) { return; } d->senders = senders; d->createSendersAndRecipients(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setRecipients(const std::vector<Recipient> &recipients) { if (d->recipients == recipients) { return; } d->recipients = recipients; d->createSendersAndRecipients(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::pickProtocol() { if (selectedProtocol() != UnknownProtocol) { return; // already picked } const bool pgp = d->isComplete(OpenPGP); const bool cms = d->isComplete(CMS); if (pgp && !cms) { d->ui.pgpRB.setChecked(true); } else if (cms && !pgp) { d->ui.cmsRB.setChecked(true); } } bool SignEncryptEMailConflictDialog::isComplete() const { const Protocol proto = selectedProtocol(); return proto != UnknownProtocol && d->isComplete(proto); } bool SignEncryptEMailConflictDialog::Private::isComplete(Protocol proto) const { return (!sign || std::none_of(ui.signers.cbegin(), ui.signers.cend(), [proto](const CertificateSelectionLine &l) { return l.isStillAmbiguous(proto); })) && (!encrypt || std::none_of(ui.recipients.cbegin(), ui.recipients.cend(), [proto](const CertificateSelectionLine &l) { return l.isStillAmbiguous(proto); })); } static std::vector<Key> get_keys(const std::vector<CertificateSelectionLine> &lines, Protocol proto) { if (proto == UnknownProtocol) { return std::vector<Key>(); } Q_ASSERT(proto == OpenPGP || proto == CMS); std::vector<Key> keys; keys.reserve(lines.size()); std::transform(lines.cbegin(), lines.cend(), std::back_inserter(keys), [proto](const CertificateSelectionLine &l) { return l.key(proto); }); return keys; } std::vector<Key> SignEncryptEMailConflictDialog::resolvedSigningKeys() const { return d->sign ? get_keys(d->ui.signers, selectedProtocol()) : std::vector<Key>(); } std::vector<Key> SignEncryptEMailConflictDialog::resolvedEncryptionKeys() const { return d->encrypt ? get_keys(d->ui.recipients, selectedProtocol()) : std::vector<Key>(); } void SignEncryptEMailConflictDialog::setQuickMode(bool on) { d->ui.quickModeCB.setChecked(on); } bool SignEncryptEMailConflictDialog::isQuickMode() const { return d->ui.quickModeCB.isChecked(); } void SignEncryptEMailConflictDialog::setConflict(bool conflict) { d->ui.conflictTopLB.setVisible(conflict); d->ui.quickModeTopLB.setVisible(!conflict); } #include "moc_signencryptemailconflictdialog.cpp" diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp index 31d87b968..50c8f3c6c 100644 --- a/src/crypto/gui/signencryptfileswizard.cpp +++ b/src/crypto/gui/signencryptfileswizard.cpp @@ -1,518 +1,519 @@ /* crypto/gui/signencryptfileswizard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 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 <config-kleopatra.h> #include "signencryptfileswizard.h" #include "signencryptwidget.h" #include "newresultpage.h" #include <KLocalizedString> #include <KSharedConfig> #include <KColorScheme> #include <KConfigGroup> #include <KWindowConfig> #include <KMessageBox> #include "kleopatra_debug.h" #include <Libkleo/GnuPG> #include <Libkleo/Formatting> #include <Libkleo/FileNameRequester> #include <QWindow> #include <QVBoxLayout> #include <QWizardPage> #include <QGroupBox> #include <QLabel> #include <QIcon> #include <QCheckBox> #include <gpgme++/key.h> using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto::Gui; enum Page { SigEncPageId, ResultPageId, NumPages }; class SigEncPage: public QWizardPage { Q_OBJECT public: explicit SigEncPage(QWidget *parent = nullptr) : QWizardPage(parent), mParent((SignEncryptFilesWizard *) parent), mWidget(new SignEncryptWidget), mOutLayout(new QVBoxLayout), mArchive(false), mUseOutputDir(false) { setTitle(i18nc("@title", "Sign / Encrypt Files")); auto vLay = new QVBoxLayout(this); vLay->setContentsMargins(0, 0, 0, 0); vLay->addWidget(mWidget); connect(mWidget, &SignEncryptWidget::operationChanged, this, &SigEncPage::updateCommitButton); connect(mWidget, &SignEncryptWidget::keysChanged, this, &SigEncPage::updateFileWidgets); updateCommitButton(mWidget->currentOp()); auto outputGrp = new QGroupBox(i18n("Output")); outputGrp->setLayout(mOutLayout); mPlaceholderWidget = new QLabel(i18n("Please select an action.")); mOutLayout->addWidget(mPlaceholderWidget); mUseOutputDirChk = new QCheckBox(i18n("Encrypt / Sign each file separately.")); mUseOutputDirChk->setToolTip(i18nc("@info", "Keep each file separate instead of creating an archive for all.")); mOutLayout->addWidget(mUseOutputDirChk); connect (mUseOutputDirChk, &QCheckBox::toggled, this, [this] (bool state) { mUseOutputDir = state; mArchive = !mUseOutputDir; updateFileWidgets(); }); vLay->addWidget(outputGrp); setMinimumHeight(300); } void setEncryptionPreset(bool value) { mWidget->setEncryptionChecked(value); } void setSigningPreset(bool value) { mWidget->setSigningChecked(value); } bool isComplete() const override { return !mWidget->currentOp().isNull(); } int nextId() const override { return ResultPageId; } void initializePage() override { setCommitPage(true); } void setArchiveForced(bool archive) { mArchive = archive; setArchiveMutable(!archive); } void setArchiveMutable(bool archive) { mUseOutputDirChk->setVisible(archive); if (archive) { const KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); mUseOutputDirChk->setChecked(archCfg.readEntry("LastUseOutputDir", false)); } else { mUseOutputDirChk->setChecked(false); } } bool validatePage() override { bool sign = !mWidget->signKey().isNull(); bool encrypt = !mWidget->selfKey().isNull() || !mWidget->recipients().empty(); if (!mWidget->validate()) { return false; } mWidget->saveOwnKeys(); if (mUseOutputDirChk->isVisible()) { KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); archCfg.writeEntry("LastUseOutputDir", mUseOutputDir); } if (sign && !encrypt && mArchive) { return KMessageBox::warningContinueCancel(this, xi18nc("@info", "<para>Archiving in combination with sign-only currently requires what are known as opaque signatures - " "unlike detached ones, these embed the content in the signature.</para>" "<para>This format is rather unusual. You might want to archive the files separately, " "and then sign the archive as one file with Kleopatra.</para>" "<para>Future versions of Kleopatra are expected to also support detached signatures in this case.</para>"), i18nc("@title:window", "Unusual Signature Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("signencryptfileswizard-archive+sign-only-warning")) == KMessageBox::Continue; } else if (sign && !encrypt) { return true; } if (!mWidget->selfKey().isNull()) { return true; } bool hasSecret = false; Q_FOREACH (const Key k, mWidget->recipients()) { if (k.hasSecret()) { hasSecret = true; break; } } if (!hasSecret && !mWidget->encryptSymmetric()) { if (KMessageBox::warningContinueCancel(this, xi18nc("@info", "<para>None of the recipients you are encrypting to seems to be your own.</para>" "<para>This means that you will not be able to decrypt the data anymore, once encrypted.</para>" "<para>Do you want to continue, or cancel to change the recipient selection?</para>"), i18nc("@title:window", "Encrypt-To-Self Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-encrypt-to-non-self"), KMessageBox::Notify | KMessageBox::Dangerous) == KMessageBox::Cancel) { return false; } } return true; } QVector<Key> recipients() const { return mWidget->recipients(); } /* In the future we might find a usecase for multiple * signers */ QVector<Key> signers() const { QVector<Key> ret; const Key k = mWidget->signKey(); if (!k.isNull()) { ret << k; } return ret; } private: QWidget *createRequester(int forKind, QBoxLayout *lay) { static const QMap <int, QString> icons = { { SignEncryptFilesWizard::SignatureCMS, QStringLiteral("document-sign") }, { SignEncryptFilesWizard::SignaturePGP, QStringLiteral("document-sign") }, { SignEncryptFilesWizard::CombinedPGP, QStringLiteral("document-edit-sign-encrypt") }, { SignEncryptFilesWizard::EncryptedPGP, QStringLiteral("document-encrypt") }, { SignEncryptFilesWizard::EncryptedCMS, QStringLiteral("document-encrypt") }, { SignEncryptFilesWizard::Directory, QStringLiteral("folder") } }; static const QMap <int, QString> toolTips = { { SignEncryptFilesWizard::SignatureCMS, i18n("The S/MIME signature.") }, { SignEncryptFilesWizard::SignaturePGP, i18n("The signature.") }, { SignEncryptFilesWizard::CombinedPGP, i18n("The signed and encrypted file.") }, { SignEncryptFilesWizard::EncryptedPGP, i18n("The encrypted file.") }, { SignEncryptFilesWizard::EncryptedCMS, i18n("The S/MIME encrypted file.") }, { SignEncryptFilesWizard::Directory, i18n("Output directory.") } }; FileNameRequester *req = new FileNameRequester(forKind == SignEncryptFilesWizard::Directory ? QDir::Dirs : QDir::Files, this); req->setFileName(mOutNames[forKind]); QHBoxLayout *hLay = new QHBoxLayout; QLabel *iconLabel = new QLabel; QWidget *ret = new QWidget; iconLabel->setPixmap(QIcon::fromTheme(icons[forKind]).pixmap(32,32)); hLay->addWidget(iconLabel); iconLabel->setToolTip(toolTips[forKind]); req->setToolTip(toolTips[forKind]); hLay->addWidget(req); ret->setLayout(hLay); lay->addWidget(ret); connect (req, &FileNameRequester::fileNameChanged, this, [this, forKind](const QString &newName) { mOutNames[forKind] = newName; }); return ret; } public: void setOutputNames(const QMap<int, QString> &names) { Q_ASSERT(mOutNames.isEmpty()); mOutNames = names; - Q_FOREACH (int i, mOutNames.keys()) { + const auto keys = mOutNames.keys(); + for (int i : keys) { mRequester[i] = createRequester(i, mOutLayout); } updateFileWidgets(); } QMap <int, QString> outputNames() const { if (!mUseOutputDir) { auto ret = mOutNames; ret.remove(SignEncryptFilesWizard::Directory); return ret; } return mOutNames; } bool encryptSymmetric() const { return mWidget->encryptSymmetric(); } private Q_SLOTS: void updateCommitButton(const QString &label) { auto btn = mParent->button(QWizard::CommitButton); if (!label.isEmpty()) { btn->setText(label); if (Kleo::gpgComplianceP("de-vs")) { bool de_vs = mWidget->isDeVsAndValid(); btn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); btn->setStyleSheet(QStringLiteral("background-color: ") + (de_vs ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); mParent->setLabelText(de_vs ? 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()) : 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())); } } else { btn->setText(i18n("Next")); btn->setIcon(QIcon()); btn->setStyleSheet(QString()); } Q_EMIT completeChanged(); } void updateFileWidgets() { if (mRequester.isEmpty()) { return; } const QVector<Key> recipients = mWidget->recipients(); const Key sigKey = mWidget->signKey(); bool pgp = mWidget->encryptSymmetric(); bool cms = false; for (const Key &k : recipients) { if (pgp && cms) { break; } if (k.protocol() == Protocol::OpenPGP) { pgp = true; } else { cms = true; } } mOutLayout->setEnabled(false); mPlaceholderWidget->setVisible(!cms && !pgp && sigKey.isNull()); mRequester[SignEncryptFilesWizard::SignatureCMS]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::CMS); mRequester[SignEncryptFilesWizard::EncryptedCMS]->setVisible(!mUseOutputDir && cms); mRequester[SignEncryptFilesWizard::CombinedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && pgp); mRequester[SignEncryptFilesWizard::EncryptedPGP]->setVisible(!mUseOutputDir && pgp && sigKey.protocol() != Protocol::OpenPGP); mRequester[SignEncryptFilesWizard::SignaturePGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && !pgp); mRequester[SignEncryptFilesWizard::Directory]->setVisible(mUseOutputDir && !mPlaceholderWidget->isVisible()); mOutLayout->setEnabled(true); } private: SignEncryptFilesWizard *mParent; SignEncryptWidget *mWidget; QMap <int, QString> mOutNames; QMap <int, QWidget *> mRequester; QVBoxLayout *mOutLayout; QWidget *mPlaceholderWidget; QCheckBox *mUseOutputDirChk; bool mArchive; bool mUseOutputDir; }; class ResultPage : public NewResultPage { Q_OBJECT public: explicit ResultPage(QWidget *parent = nullptr) : NewResultPage(parent), mParent((SignEncryptFilesWizard *) parent) { setTitle(i18nc("@title", "Results")); setSubTitle(i18nc("@title", "Status and progress of the crypto operations is shown here.")); } void initializePage() override { mParent->setLabelText(QString()); } private: SignEncryptFilesWizard *mParent; }; SignEncryptFilesWizard::SignEncryptFilesWizard(QWidget *parent, Qt::WindowFlags f) : QWizard(parent, f) { readConfig(); bool de_vs = Kleo::gpgComplianceP("de-vs"); #ifdef Q_OS_WIN // Enforce modern style to avoid vista style ugliness. setWizardStyle(QWizard::ModernStyle); #endif mSigEncPage = new SigEncPage(this); mResultPage = new ResultPage(this); connect(this, &QWizard::currentIdChanged, this, &SignEncryptFilesWizard::slotCurrentIdChanged); setPage(SigEncPageId, mSigEncPage); setPage(ResultPageId, mResultPage); setOptions(QWizard::IndependentPages | (de_vs ? QWizard::HaveCustomButton1 : (QWizard::WizardOption) 0) | QWizard::NoBackButtonOnLastPage | QWizard::NoBackButtonOnStartPage); if (de_vs) { /* We use a custom button to display a label next to the buttons. */ mLabel = button(QWizard::CustomButton1); /* We style the button so that it looks and acts like a label. */ mLabel->setStyleSheet(QStringLiteral("border: none")); mLabel->setFocusPolicy(Qt::NoFocus); } else { mLabel = nullptr; } } void SignEncryptFilesWizard::setLabelText(const QString &label) const { if (mLabel) { mLabel->setText(label); } } void SignEncryptFilesWizard::slotCurrentIdChanged(int id) { if (id == ResultPageId) { Q_EMIT operationPrepared(); } } SignEncryptFilesWizard::~SignEncryptFilesWizard() { qCDebug(KLEOPATRA_LOG); writeConfig(); } void SignEncryptFilesWizard::setSigningPreset(bool preset) { mSigEncPage->setSigningPreset(preset); } void SignEncryptFilesWizard::setSigningUserMutable(bool mut) { if (mut == mSigningUserMutable) { return; } mSigningUserMutable = mut; } void SignEncryptFilesWizard::setEncryptionPreset(bool preset) { mSigEncPage->setEncryptionPreset(preset); } void SignEncryptFilesWizard::setEncryptionUserMutable(bool mut) { if (mut == mEncryptionUserMutable) { return; } mEncryptionUserMutable = mut; } void SignEncryptFilesWizard::setArchiveForced(bool archive) { mSigEncPage->setArchiveForced(archive); } void SignEncryptFilesWizard::setArchiveMutable(bool archive) { mSigEncPage->setArchiveMutable(archive); } QVector<Key> SignEncryptFilesWizard::resolvedRecipients() const { return mSigEncPage->recipients(); } QVector<Key> SignEncryptFilesWizard::resolvedSigners() const { return mSigEncPage->signers(); } void SignEncryptFilesWizard::setTaskCollection(const std::shared_ptr<Kleo::Crypto::TaskCollection> &coll) { mResultPage->setTaskCollection(coll); } void SignEncryptFilesWizard::setOutputNames(const QMap<int, QString> &map) const { mSigEncPage->setOutputNames(map); } QMap<int, QString> SignEncryptFilesWizard::outputNames() const { return mSigEncPage->outputNames(); } bool SignEncryptFilesWizard::encryptSymmetric() const { return mSigEncPage->encryptSymmetric(); } void SignEncryptFilesWizard::readConfig() { winId(); // ensure there's a window created // set default window size windowHandle()->resize(640, 480); // restore size from config file KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); } void SignEncryptFilesWizard::writeConfig() { KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::saveWindowSize(windowHandle(), cfgGroup); cfgGroup.sync(); } #include "signencryptfileswizard.moc" diff --git a/src/crypto/gui/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp index 230db7c53..6939bc8d5 100644 --- a/src/crypto/gui/signencryptwidget.cpp +++ b/src/crypto/gui/signencryptwidget.cpp @@ -1,521 +1,521 @@ /* crypto/gui/signencryptwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "signencryptwidget.h" #include "kleopatra_debug.h" #include "certificatelineedit.h" #include "unknownrecipientwidget.h" #include <QVBoxLayout> #include <QHBoxLayout> #include <QGroupBox> #include <QCheckBox> #include <QScrollArea> #include <QScrollBar> #include <Libkleo/DefaultKeyFilter> #include <Libkleo/KeyCache> #include <Libkleo/KeyListModel> #include <Libkleo/KeySelectionCombo> #include <Libkleo/KeyListSortFilterProxyModel> #include <Libkleo/GnuPG> #include <KLocalizedString> #include <KConfigGroup> #include <KSharedConfig> #include <KMessageBox> using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; namespace { class SignCertificateFilter: public DefaultKeyFilter { public: SignCertificateFilter(GpgME::Protocol proto) : DefaultKeyFilter() { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); setCanSign(DefaultKeyFilter::Set); if (proto == GpgME::OpenPGP) { setIsOpenPGP(DefaultKeyFilter::Set); } else if (proto == GpgME::CMS) { setIsOpenPGP(DefaultKeyFilter::NotSet); } } }; class EncryptCertificateFilter: public DefaultKeyFilter { public: EncryptCertificateFilter(GpgME::Protocol proto): DefaultKeyFilter() { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); if (proto == GpgME::OpenPGP) { setIsOpenPGP(DefaultKeyFilter::Set); } else if (proto == GpgME::CMS) { setIsOpenPGP(DefaultKeyFilter::NotSet); } } }; class EncryptSelfCertificateFilter: public EncryptCertificateFilter { public: EncryptSelfCertificateFilter(GpgME::Protocol proto): EncryptCertificateFilter(proto) { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setCanEncrypt(DefaultKeyFilter::Set); setHasSecret(DefaultKeyFilter::Set); } }; } SignEncryptWidget::SignEncryptWidget(QWidget *parent, bool sigEncExclusive) : QWidget(parent), mModel(AbstractKeyListModel::createFlatKeyListModel(this)), mRecpRowCount(2), mIsExclusive(sigEncExclusive) { QVBoxLayout *lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); mModel->useKeyCache(true, false); /* The signature selection */ QHBoxLayout *sigLay = new QHBoxLayout; QGroupBox *sigGrp = new QGroupBox(i18n("Prove authenticity (sign)")); mSigChk = new QCheckBox(i18n("Sign as:")); mSigChk->setChecked(true); mSigSelect = new KeySelectionCombo(); sigLay->addWidget(mSigChk); sigLay->addWidget(mSigSelect, 1); sigGrp->setLayout(sigLay); lay->addWidget(sigGrp); connect(mSigChk, &QCheckBox::toggled, mSigSelect, &QWidget::setEnabled); connect(mSigChk, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); connect(mSigSelect, &KeySelectionCombo::currentKeyChanged, this, &SignEncryptWidget::updateOp); // Recipient selection mRecpLayout = new QGridLayout; mRecpLayout->setAlignment(Qt::AlignTop); QVBoxLayout *encBoxLay = new QVBoxLayout; QGroupBox *encBox = new QGroupBox(i18nc("@action", "Encrypt")); encBox->setLayout(encBoxLay); encBox->setAlignment(Qt::AlignLeft); // Own key mSelfSelect = new KeySelectionCombo(); mEncSelfChk = new QCheckBox(i18n("Encrypt for me:")); mEncSelfChk->setChecked(true); mRecpLayout->addWidget(mEncSelfChk, 0, 0); mRecpLayout->addWidget(mSelfSelect, 0, 1); // Checkbox for other keys mEncOtherChk = new QCheckBox(i18n("Encrypt for others:")); mRecpLayout->addWidget(mEncOtherChk, 1, 0); mEncOtherChk->setChecked(true); connect(mEncOtherChk, &QCheckBox::toggled, this, [this](bool toggled) { - Q_FOREACH (CertificateLineEdit *edit, mRecpWidgets) { + for (CertificateLineEdit *edit : qAsConst(mRecpWidgets)) { edit->setEnabled(toggled); } updateOp(); }); // Scroll area for other keys QWidget *recipientWidget = new QWidget; QScrollArea *recipientScroll = new QScrollArea; recipientWidget->setLayout(mRecpLayout); recipientScroll->setWidget(recipientWidget); recipientScroll->setWidgetResizable(true); recipientScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); recipientScroll->setFrameStyle(QFrame::NoFrame); mRecpLayout->setContentsMargins(0, 0, 0, 0); encBoxLay->addWidget(recipientScroll, 1); auto bar = recipientScroll->verticalScrollBar(); connect (bar, &QScrollBar::rangeChanged, this, [bar] (int, int max) { bar->setValue(max); }); // Checkbox for password mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data.")); mSymmetric->setToolTip(i18nc("Tooltip information for symetric encryption", "Additionally to the keys of the recipients you can encrypt your data with a password. " "Anyone who has the password can read the data without any secret key. " "Using a password is <b>less secure</b> then public key cryptography. Even if you pick a very strong password.")); encBoxLay->addWidget(mSymmetric); // Connect it connect(encBox, &QGroupBox::toggled, recipientWidget, &QWidget::setEnabled); connect(encBox, &QGroupBox::toggled, this, &SignEncryptWidget::updateOp); connect(mEncSelfChk, &QCheckBox::toggled, mSelfSelect, &QWidget::setEnabled); connect(mEncSelfChk, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); connect(mSymmetric, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); connect(mSelfSelect, &KeySelectionCombo::currentKeyChanged, this, &SignEncryptWidget::updateOp); if (mIsExclusive) { connect(mEncOtherChk, &QCheckBox::toggled, this, [this](bool value) { if (mCurrentProto != GpgME::CMS) { return; } if (value) { mSigChk->setChecked(false); } }); connect(mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) { if (mCurrentProto != GpgME::CMS) { return; } if (value) { mSigChk->setChecked(false); } }); connect(mSigChk, &QCheckBox::toggled, this, [this](bool value) { if (mCurrentProto != GpgME::CMS) { return; } if (value) { mEncSelfChk->setChecked(false); mEncOtherChk->setChecked(false); } }); } // Ensure that the mSigChk is aligned togehter with the encryption check boxes. mSigChk->setMinimumWidth(qMax(mEncOtherChk->width(), mEncSelfChk->width())); lay->addWidget(encBox); loadKeys(); setProtocol(GpgME::UnknownProtocol); addRecipient(Key()); updateOp(); } void SignEncryptWidget::addRecipient() { addRecipient(Key()); } void SignEncryptWidget::addRecipient(const Key &key) { CertificateLineEdit *certSel = new CertificateLineEdit(mModel, this, new EncryptCertificateFilter(mCurrentProto)); mRecpWidgets << certSel; if (!mRecpLayout->itemAtPosition(mRecpRowCount - 1, 1)) { // First widget. Should align with the row above that // contains the encrypt for others checkbox. mRecpLayout->addWidget(certSel, mRecpRowCount - 1, 1); } else { mRecpLayout->addWidget(certSel, mRecpRowCount++, 1); } connect(certSel, &CertificateLineEdit::keyChanged, this, &SignEncryptWidget::recipientsChanged); connect(certSel, &CertificateLineEdit::wantsRemoval, this, &SignEncryptWidget::recpRemovalRequested); connect(certSel, &CertificateLineEdit::editingStarted, this, static_cast<void (SignEncryptWidget::*)()>(&SignEncryptWidget::addRecipient)); connect(certSel, &CertificateLineEdit::addRequested, this, static_cast<void (SignEncryptWidget::*)(const Key&)>(&SignEncryptWidget::addRecipient)); if (!key.isNull()) { certSel->setKey(key); mAddedKeys << key; } } void SignEncryptWidget::clearAddedRecipients() { for (auto w: qAsConst(mUnknownWidgets)) { mRecpLayout->removeWidget(w); delete w; } for (auto &key: qAsConst(mAddedKeys)) { removeRecipient(key); } } void SignEncryptWidget::addUnknownRecipient(const char *keyID) { auto unknownWidget = new UnknownRecipientWidget(keyID); mUnknownWidgets << unknownWidget; if (!mRecpLayout->itemAtPosition(mRecpRowCount - 1, 1)) { // First widget. Should align with the row above that // contains the encrypt for others checkbox. mRecpLayout->addWidget(unknownWidget, mRecpRowCount - 1, 1); } else { mRecpLayout->addWidget(unknownWidget, mRecpRowCount++, 1); } connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this] () { // Check if any unknown recipient can now be found. for (auto w: mUnknownWidgets) { auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData()); if (key.isNull()) { std::vector<std::string> subids; subids.push_back(std::string(w->keyID().toLatin1().constData())); for (const auto &subkey: KeyCache::instance()->findSubkeysByKeyID(subids)) { key = subkey.parent(); } } if (key.isNull()) { continue; } // Key is now available replace by line edit. qCDebug(KLEOPATRA_LOG) << "Removing widget for keyid: " << w->keyID(); mRecpLayout->removeWidget(w); mUnknownWidgets.removeAll(w); delete w; addRecipient(key); } }); } void SignEncryptWidget::recipientsChanged() { bool oneEmpty = false; - Q_FOREACH (const CertificateLineEdit *w, mRecpWidgets) { + for (const CertificateLineEdit *w : qAsConst(mRecpWidgets)) { if (w->key().isNull()) { oneEmpty = true; break; } } if (!oneEmpty) { addRecipient(); } updateOp(); } Key SignEncryptWidget::signKey() const { if (mSigSelect->isEnabled()) { return mSigSelect->currentKey(); } return Key(); } Key SignEncryptWidget::selfKey() const { if (mSelfSelect->isEnabled()) { return mSelfSelect->currentKey(); } return Key(); } QVector <Key> SignEncryptWidget::recipients() const { QVector<Key> ret; - Q_FOREACH (const CertificateLineEdit *w, mRecpWidgets) { + for (const CertificateLineEdit *w : qAsConst(mRecpWidgets)) { if (!w->isEnabled()) { // If one is disabled, all are disabled. break; } const Key k = w->key(); if (!k.isNull()) { ret << k; } } const Key k = selfKey(); if (!k.isNull()) { ret << k; } return ret; } bool SignEncryptWidget::isDeVsAndValid() const { if (!signKey().isNull() && (!IS_DE_VS(signKey()) || keyValidity(signKey()) < GpgME::UserID::Validity::Full)) { return false; } if (!selfKey().isNull() && (!IS_DE_VS(selfKey()) || keyValidity(selfKey()) < GpgME::UserID::Validity::Full)) { return false; } for (const auto &key: recipients()) { if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } return true; } void SignEncryptWidget::updateOp() { const Key sigKey = signKey(); const QVector<Key> recp = recipients(); QString newOp; if (!sigKey.isNull() && (!recp.isEmpty() || encryptSymmetric())) { newOp = i18nc("@action", "Sign / Encrypt"); } else if (!recp.isEmpty() || encryptSymmetric()) { newOp = i18nc("@action", "Encrypt"); } else if (!sigKey.isNull()) { newOp = i18nc("@action", "Sign"); } else { newOp = QString(); } mOp = newOp; Q_EMIT operationChanged(mOp); Q_EMIT keysChanged(); } QString SignEncryptWidget::currentOp() const { return mOp; } void SignEncryptWidget::recpRemovalRequested(CertificateLineEdit *w) { if (!w) { return; } int emptyEdits = 0; - Q_FOREACH (const CertificateLineEdit *edit, mRecpWidgets) { + for (const CertificateLineEdit *edit : qAsConst(mRecpWidgets)) { if (edit->isEmpty()) { emptyEdits++; } if (emptyEdits > 1) { int row, col, rspan, cspan; mRecpLayout->getItemPosition(mRecpLayout->indexOf(w), &row, &col, &rspan, &cspan); mRecpLayout->removeWidget(w); mRecpWidgets.removeAll(w); // The row count of the grid layout does not reflect the actual // items so we keep our internal count. mRecpRowCount--; for (int i = row + 1; i <= mRecpRowCount; i++) { // move widgets one up auto item = mRecpLayout->itemAtPosition(i, 1); if (!item) { break; } mRecpLayout->removeItem(item); mRecpLayout->addItem(item, i - 1, 1); } w->deleteLater(); return; } } } void SignEncryptWidget::removeRecipient(const GpgME::Key &key) { for (CertificateLineEdit *edit: qAsConst(mRecpWidgets)) { const auto editKey = edit->key(); if (key.isNull() && editKey.isNull()) { recpRemovalRequested(edit); return; } if (editKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) { recpRemovalRequested(edit); return; } } } bool SignEncryptWidget::encryptSymmetric() const { return mSymmetric->isChecked(); } void SignEncryptWidget::loadKeys() { KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys"); auto cache = KeyCache::instance(); mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString())); mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString())); } void SignEncryptWidget::saveOwnKeys() const { KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys"); auto sigKey = mSigSelect->currentKey(); auto encKey = mSelfSelect->currentKey(); if (!sigKey.isNull()) { keys.writeEntry("SigningKey", sigKey.primaryFingerprint()); } if (!encKey.isNull()) { keys.writeEntry("EncryptKey", encKey.primaryFingerprint()); } } void SignEncryptWidget::setSigningChecked(bool value) { mSigChk->setChecked(value); } void SignEncryptWidget::setEncryptionChecked(bool value) { mEncSelfChk->setChecked(value); mEncOtherChk->setChecked(value); } void SignEncryptWidget::setProtocol(GpgME::Protocol proto) { if (mCurrentProto == proto) { return; } mCurrentProto = proto; mSigSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new SignCertificateFilter(proto))); mSelfSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new EncryptSelfCertificateFilter(proto))); const auto encFilter = std::shared_ptr<KeyFilter>(new EncryptCertificateFilter(proto)); - Q_FOREACH (CertificateLineEdit *edit, mRecpWidgets) { + for (CertificateLineEdit *edit : qAsConst(mRecpWidgets)) { edit->setKeyFilter(encFilter); } if (mIsExclusive) { mSymmetric->setDisabled(proto == GpgME::CMS); if (mSymmetric->isChecked() && proto == GpgME::CMS) { mSymmetric->setChecked(false); } if (mSigChk->isChecked() && proto == GpgME::CMS && (mEncSelfChk->isChecked() || mEncOtherChk->isChecked())) { mSigChk->setChecked(false); } } } bool SignEncryptWidget::validate() { for (const auto edit: qAsConst(mRecpWidgets)) { if (!edit->isEmpty() && edit->key().isNull()) { KMessageBox::error(this, i18nc("%1 is user input that could not be found", "Could not find a key for '%1'", edit->text().toHtmlEscaped()), i18n("Failed to find recipient"), KMessageBox::Notify); return false; } } return true; } diff --git a/src/crypto/newsignencryptemailcontroller.cpp b/src/crypto/newsignencryptemailcontroller.cpp index f6f076458..0ba88e301 100644 --- a/src/crypto/newsignencryptemailcontroller.cpp +++ b/src/crypto/newsignencryptemailcontroller.cpp @@ -1,632 +1,632 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/newsignencryptemailcontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009, 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "newsignencryptemailcontroller.h" #include "kleopatra_debug.h" #include "encryptemailtask.h" #include "signemailtask.h" #include "taskcollection.h" #include "sender.h" #include "recipient.h" #include "emailoperationspreferences.h" #include <crypto/gui/signencryptemailconflictdialog.h> #include "utils/input.h" #include "utils/output.h" #include <Libkleo/GnuPG> #include "utils/kleo_assert.h" #include <Libkleo/Stl_Util> #include <Libkleo/KleoException> #include <gpgme++/key.h> #include <kmime/kmime_header_parsing.h> #include <KLocalizedString> #include <KMessageBox> #include <QPointer> #include <QTimer> using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace GpgME; using namespace KMime::Types; // // BEGIN Conflict Detection // /* This code implements the following conflict detection algorithm: 1. There is no conflict if and only if we have a Perfect Match. 2. A Perfect Match is defined as: a. either a Perfect OpenPGP-Match and not even a Partial S/MIME Match b. or a Perfect S/MIME-Match and not even a Partial OpenPGP-Match c. or a Perfect OpenPGP-Match and preselected protocol=OpenPGP d. or a Perfect S/MIME-Match and preselected protocol=S/MIME 3. For Protocol \in {OpenPGP,S/MIME}, a Perfect Protocol-Match is defined as: a. If signing, \foreach Sender, there is exactly one Matching Protocol-Certificate with i. can-sign=true ii. has-secret=true b. and, if encrypting, \foreach Recipient, there is exactly one Matching Protocol-Certificate with i. can-encrypt=true ii. (validity is not considered, cf. msg 24059) 4. For Protocol \in {OpenPGP,S/MIME}, a Partial Protocol-Match is defined as: a. If signing, \foreach Sender, there is at least one Matching Protocol-Certificate with i. can-sign=true ii. has-secret=true b. and, if encrypting, \foreach Recipient, there is at least one Matching Protocol-Certificate with i. can-encrypt=true ii. (validity is not considered, cf. msg 24059) 5. For Protocol \in {OpenPGP,S/MIME}, a Matching Protocol-Certificate is defined as matching by email-address. A revoked, disabled, or expired certificate is not considered a match. 6. Sender is defined as those mailboxes that have been set with the SENDER command. 7. Recipient is defined as those mailboxes that have been set with either the SENDER or the RECIPIENT commands. */ namespace { static size_t count_signing_certificates(Protocol proto, const Sender &sender) { const size_t result = sender.signingCertificateCandidates(proto).size(); qDebug("count_signing_certificates( %9s %20s ) == %2lu", proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : "<unknown>,", qPrintable(sender.mailbox().prettyAddress()), result); return result; } static size_t count_encrypt_certificates(Protocol proto, const Sender &sender) { const size_t result = sender.encryptToSelfCertificateCandidates(proto).size(); qDebug("count_encrypt_certificates( %9s %20s ) == %2lu", proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : "<unknown>,", qPrintable(sender.mailbox().prettyAddress()), result); return result; } static size_t count_encrypt_certificates(Protocol proto, const Recipient &recipient) { const size_t result = recipient.encryptionCertificateCandidates(proto).size(); qDebug("count_encrypt_certificates( %9s %20s ) == %2lu", proto == OpenPGP ? "OpenPGP," : proto == CMS ? "CMS," : "<unknown>,", qPrintable(recipient.mailbox().prettyAddress()), result); return result; } } static bool has_perfect_match(bool sign, bool encrypt, Protocol proto, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients) { if (sign) if (!std::all_of(senders.cbegin(), senders.cend(), [proto](const Sender &sender) { return count_signing_certificates(proto, sender) == 1; })) { return false; } if (encrypt) if (!std::all_of(senders.cbegin(), senders.cend(), [proto](const Sender &sender) { return count_encrypt_certificates(proto, sender) == 1; }) || !std::all_of(recipients.cbegin(), recipients.cend(), [proto](const Recipient &rec) { return count_encrypt_certificates(proto, rec) == 1; })) { return false; } return true; } static bool has_partial_match(bool sign, bool encrypt, Protocol proto, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients) { if (sign) if (std::all_of(senders.cbegin(), senders.cend(), [proto](const Sender &sender) { return count_signing_certificates(proto, sender) >= 1; })) { return false; } if (encrypt) if (!std::all_of(senders.cbegin(), senders.cend(), [proto](const Sender &sender) { return count_encrypt_certificates(proto, sender) >= 1; }) || !std::all_of(recipients.cbegin(), recipients.cend(), [proto](const Recipient &rec) { return count_encrypt_certificates(proto, rec) >= 1; })) { return false; } return true; } static bool has_perfect_overall_match(bool sign, bool encrypt, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients, Protocol presetProtocol) { return (presetProtocol == OpenPGP && has_perfect_match(sign, encrypt, OpenPGP, senders, recipients)) || (presetProtocol == CMS && has_perfect_match(sign, encrypt, CMS, senders, recipients)) || (has_perfect_match(sign, encrypt, OpenPGP, senders, recipients) && !has_partial_match(sign, encrypt, CMS, senders, recipients)) || (has_perfect_match(sign, encrypt, CMS, senders, recipients) && !has_partial_match(sign, encrypt, OpenPGP, senders, recipients)); } static bool has_conflict(bool sign, bool encrypt, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients, Protocol presetProtocol) { return !has_perfect_overall_match(sign, encrypt, senders, recipients, presetProtocol); } static bool is_de_vs_compliant(bool sign, bool encrypt, const std::vector<Sender> &senders, const std::vector<Recipient> &recipients, Protocol presetProtocol) { if (presetProtocol == Protocol::UnknownProtocol) { return false; } if (sign) { for (const auto &sender: senders) { const auto &key = sender.resolvedSigningKey(presetProtocol); if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } } if (encrypt) { for (const auto &sender: senders) { const auto &key = sender.resolvedSigningKey(presetProtocol); if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } for (const auto &recipient: recipients) { const auto &key = recipient.resolvedEncryptionKey(presetProtocol); if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } } return true; } // // END Conflict Detection // static std::vector<Sender> mailbox2sender(const std::vector<Mailbox> &mbs) { std::vector<Sender> senders; senders.reserve(mbs.size()); for (const Mailbox &mb : mbs) { senders.push_back(Sender(mb)); } return senders; } static std::vector<Recipient> mailbox2recipient(const std::vector<Mailbox> &mbs) { std::vector<Recipient> recipients; recipients.reserve(mbs.size()); for (const Mailbox &mb : mbs) { recipients.push_back(Recipient(mb)); } return recipients; } class NewSignEncryptEMailController::Private { friend class ::Kleo::Crypto::NewSignEncryptEMailController; NewSignEncryptEMailController *const q; public: explicit Private(NewSignEncryptEMailController *qq); ~Private(); private: void slotDialogAccepted(); void slotDialogRejected(); private: void ensureDialogVisible(); void cancelAllTasks(); void startSigning(); void startEncryption(); void schedule(); std::shared_ptr<Task> takeRunnable(GpgME::Protocol proto); private: bool sign : 1; bool encrypt : 1; bool resolvingInProgress : 1; bool certificatesResolved : 1; bool detached : 1; Protocol presetProtocol; std::vector<Key> signers, recipients; std::vector< std::shared_ptr<Task> > runnable, completed; std::shared_ptr<Task> cms, openpgp; QPointer<SignEncryptEMailConflictDialog> dialog; }; NewSignEncryptEMailController::Private::Private(NewSignEncryptEMailController *qq) : q(qq), sign(false), encrypt(false), resolvingInProgress(false), certificatesResolved(false), detached(false), presetProtocol(UnknownProtocol), signers(), recipients(), runnable(), cms(), openpgp(), dialog(new SignEncryptEMailConflictDialog) { connect(dialog, SIGNAL(accepted()), q, SLOT(slotDialogAccepted())); connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected())); } NewSignEncryptEMailController::Private::~Private() { delete dialog; } NewSignEncryptEMailController::NewSignEncryptEMailController(const std::shared_ptr<ExecutionContext> &xc, QObject *p) : Controller(xc, p), d(new Private(this)) { } NewSignEncryptEMailController::NewSignEncryptEMailController(QObject *p) : Controller(p), d(new Private(this)) { } NewSignEncryptEMailController::~NewSignEncryptEMailController() { qCDebug(KLEOPATRA_LOG); } void NewSignEncryptEMailController::setSubject(const QString &subject) { d->dialog->setSubject(subject); } void NewSignEncryptEMailController::setProtocol(Protocol proto) { d->presetProtocol = proto; d->dialog->setPresetProtocol(proto); } Protocol NewSignEncryptEMailController::protocol() const { return d->dialog->selectedProtocol(); } const char *NewSignEncryptEMailController::protocolAsString() const { switch (protocol()) { case OpenPGP: return "OpenPGP"; case CMS: return "CMS"; default: throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL), i18n("Call to NewSignEncryptEMailController::protocolAsString() is ambiguous.")); } } void NewSignEncryptEMailController::setSigning(bool sign) { d->sign = sign; d->dialog->setSign(sign); } bool NewSignEncryptEMailController::isSigning() const { return d->sign; } void NewSignEncryptEMailController::setEncrypting(bool encrypt) { d->encrypt = encrypt; d->dialog->setEncrypt(encrypt); } bool NewSignEncryptEMailController::isEncrypting() const { return d->encrypt; } void NewSignEncryptEMailController::setDetachedSignature(bool detached) { d->detached = detached; } bool NewSignEncryptEMailController::isResolvingInProgress() const { return d->resolvingInProgress; } bool NewSignEncryptEMailController::areCertificatesResolved() const { return d->certificatesResolved; } static bool is_dialog_quick_mode(bool sign, bool encrypt) { const EMailOperationsPreferences prefs; return (!sign || prefs.quickSignEMail()) && (!encrypt || prefs.quickEncryptEMail()) ; } static void save_dialog_quick_mode(bool on) { EMailOperationsPreferences prefs; prefs.setQuickSignEMail(on); prefs.setQuickEncryptEMail(on); prefs.save(); } void NewSignEncryptEMailController::startResolveCertificates(const std::vector<Mailbox> &r, const std::vector<Mailbox> &s) { d->certificatesResolved = false; d->resolvingInProgress = true; const std::vector<Sender> senders = mailbox2sender(s); const std::vector<Recipient> recipients = mailbox2recipient(r); const bool quickMode = is_dialog_quick_mode(d->sign, d->encrypt); const bool conflict = quickMode && has_conflict(d->sign, d->encrypt, senders, recipients, d->presetProtocol); d->dialog->setQuickMode(quickMode); d->dialog->setSenders(senders); d->dialog->setRecipients(recipients); d->dialog->pickProtocol(); d->dialog->setConflict(conflict); const bool compliant = !Kleo::gpgComplianceP("de-vs") || is_de_vs_compliant(d->sign, d->encrypt, senders, recipients, d->presetProtocol); if (quickMode && !conflict && compliant) { QMetaObject::invokeMethod(this, "slotDialogAccepted", Qt::QueuedConnection); } else { d->ensureDialogVisible(); } } void NewSignEncryptEMailController::Private::slotDialogAccepted() { if (dialog->isQuickMode() != is_dialog_quick_mode(sign, encrypt)) { save_dialog_quick_mode(dialog->isQuickMode()); } resolvingInProgress = false; certificatesResolved = true; signers = dialog->resolvedSigningKeys(); recipients = dialog->resolvedEncryptionKeys(); QMetaObject::invokeMethod(q, "certificatesResolved", Qt::QueuedConnection); } void NewSignEncryptEMailController::Private::slotDialogRejected() { resolvingInProgress = false; certificatesResolved = false; QMetaObject::invokeMethod(q, "error", Qt::QueuedConnection, Q_ARG(int, gpg_error(GPG_ERR_CANCELED)), Q_ARG(QString, i18n("User cancel"))); } void NewSignEncryptEMailController::startEncryption(const std::vector< std::shared_ptr<Input> > &inputs, const std::vector< std::shared_ptr<Output> > &outputs) { kleo_assert(d->encrypt); kleo_assert(!d->resolvingInProgress); kleo_assert(!inputs.empty()); kleo_assert(outputs.size() == inputs.size()); std::vector< std::shared_ptr<Task> > tasks; tasks.reserve(inputs.size()); kleo_assert(!d->recipients.empty()); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr<EncryptEMailTask> task(new EncryptEMailTask); task->setInput(inputs[i]); task->setOutput(outputs[i]); task->setRecipients(d->recipients); tasks.push_back(task); } // append to runnable stack d->runnable.insert(d->runnable.end(), tasks.begin(), tasks.end()); d->startEncryption(); } void NewSignEncryptEMailController::Private::startEncryption() { std::shared_ptr<TaskCollection> coll(new TaskCollection); std::vector<std::shared_ptr<Task> > tmp; tmp.reserve(runnable.size()); std::copy(runnable.cbegin(), runnable.cend(), std::back_inserter(tmp)); coll->setTasks(tmp); #if 0 #warning use a new result dialog // ### use a new result dialog dialog->setTaskCollection(coll); #endif - Q_FOREACH (const std::shared_ptr<Task> &t, tmp) { + for (const std::shared_ptr<Task> &t : qAsConst(tmp)) { q->connectTask(t); } schedule(); } void NewSignEncryptEMailController::startSigning(const std::vector< std::shared_ptr<Input> > &inputs, const std::vector< std::shared_ptr<Output> > &outputs) { kleo_assert(d->sign); kleo_assert(!d->resolvingInProgress); kleo_assert(!inputs.empty()); kleo_assert(!outputs.empty()); std::vector< std::shared_ptr<Task> > tasks; tasks.reserve(inputs.size()); kleo_assert(!d->signers.empty()); kleo_assert(std::none_of(d->signers.cbegin(), d->signers.cend(), std::mem_fn(&Key::isNull))); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr<SignEMailTask> task(new SignEMailTask); task->setInput(inputs[i]); task->setOutput(outputs[i]); task->setSigners(d->signers); task->setDetachedSignature(d->detached); tasks.push_back(task); } // append to runnable stack d->runnable.insert(d->runnable.end(), tasks.begin(), tasks.end()); d->startSigning(); } void NewSignEncryptEMailController::Private::startSigning() { std::shared_ptr<TaskCollection> coll(new TaskCollection); std::vector<std::shared_ptr<Task> > tmp; tmp.reserve(runnable.size()); std::copy(runnable.cbegin(), runnable.cend(), std::back_inserter(tmp)); coll->setTasks(tmp); #if 0 #warning use a new result dialog // ### use a new result dialog dialog->setTaskCollection(coll); #endif - Q_FOREACH (const std::shared_ptr<Task> &t, tmp) { + for (const std::shared_ptr<Task> &t : qAsConst(tmp)) { q->connectTask(t); } schedule(); } void NewSignEncryptEMailController::Private::schedule() { if (!cms) if (const std::shared_ptr<Task> t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr<Task> t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (cms || openpgp) { return; } kleo_assert(runnable.empty()); q->emitDoneOrError(); } std::shared_ptr<Task> NewSignEncryptEMailController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr<Task> &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr<Task>(); } const std::shared_ptr<Task> result = *it; runnable.erase(it); return result; } void NewSignEncryptEMailController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result) { Q_ASSERT(task); if (result && result->hasError()) { QPointer<QObject> that = this; if (result->details().isEmpty()) KMessageBox:: sorry(nullptr, result->overview(), i18nc("@title:window", "Error")); else KMessageBox::detailedSorry(nullptr, result->overview(), result->details(), i18nc("@title:window", "Error")); if (!that) { return; } } // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container if (task == d->cms.get()) { d->completed.push_back(d->cms); d->cms.reset(); } else if (task == d->openpgp.get()) { d->completed.push_back(d->openpgp); d->openpgp.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } void NewSignEncryptEMailController::cancel() { try { d->dialog->close(); d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void NewSignEncryptEMailController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. runnable.clear(); // a cancel() will result in a call to if (cms) { cms->cancel(); } if (openpgp) { openpgp->cancel(); } } void NewSignEncryptEMailController::Private::ensureDialogVisible() { q->bringToForeground(dialog, true); } #include "moc_newsignencryptemailcontroller.cpp" diff --git a/src/crypto/signemailcontroller.cpp b/src/crypto/signemailcontroller.cpp index a14f4395f..840bb0815 100644 --- a/src/crypto/signemailcontroller.cpp +++ b/src/crypto/signemailcontroller.cpp @@ -1,343 +1,343 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signemailcontroller.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 <config-kleopatra.h> #include "signemailcontroller.h" #include "kleopatra_debug.h" #include "signemailtask.h" #include "certificateresolver.h" #include "taskcollection.h" #include <crypto/gui/signemailwizard.h> #include <utils/input.h> #include <utils/output.h> #include <utils/kleo_assert.h> #include "emailoperationspreferences.h" #include <Libkleo/Stl_Util> #include <kmime/kmime_header_parsing.h> #include <KLocalizedString> #include <QPointer> #include <QTimer> using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace GpgME; using namespace KMime::Types; class SignEMailController::Private { friend class ::Kleo::Crypto::SignEMailController; SignEMailController *const q; public: explicit Private(Mode m, SignEMailController *qq); ~Private(); private: void slotWizardSignersResolved(); void slotWizardCanceled(); // ### extract to base private: void ensureWizardCreated(); // ### extract to base void ensureWizardVisible(); // ### extract to base void cancelAllJobs(); // ### extract to base void schedule(); // ### extract to base std::shared_ptr<SignEMailTask> takeRunnable(GpgME::Protocol proto); // ### extract to base private: const Mode mode; std::vector< std::shared_ptr<SignEMailTask> > runnable, completed; // ### extract to base std::shared_ptr<SignEMailTask> cms, openpgp; // ### extract to base QPointer<SignEMailWizard> wizard; // ### extract to base Protocol protocol; // ### extract to base bool detached : 1; }; SignEMailController::Private::Private(Mode m, SignEMailController *qq) : q(qq), mode(m), runnable(), cms(), openpgp(), wizard(), protocol(UnknownProtocol), detached(false) { } SignEMailController::Private::~Private() {} SignEMailController::SignEMailController(Mode mode, QObject *p) : Controller(p), d(new Private(mode, this)) { } SignEMailController::SignEMailController(const std::shared_ptr<ExecutionContext> &xc, Mode mode, QObject *p) : Controller(xc, p), d(new Private(mode, this)) { } SignEMailController::~SignEMailController() { /// ### extract to base if (d->wizard && !d->wizard->isVisible()) { delete d->wizard; } //d->wizard->close(); ### ? } SignEMailController::Mode SignEMailController::mode() const { return d->mode; } // ### extract to base void SignEMailController::setProtocol(Protocol proto) { kleo_assert(d->protocol == UnknownProtocol || d->protocol == proto); d->protocol = proto; d->ensureWizardCreated(); d->wizard->setPresetProtocol(proto); } Protocol SignEMailController::protocol() const { return d->protocol; } void SignEMailController::startResolveSigners() { startResolveSigners(std::vector<Mailbox>()); } void SignEMailController::startResolveSigners(const std::vector<Mailbox> &signers) { const std::vector< std::vector<Key> > keys = CertificateResolver::resolveSigners(signers, d->protocol); if (!signers.empty()) { kleo_assert(keys.size() == static_cast<size_t>(signers.size())); } d->ensureWizardCreated(); d->wizard->setSignersAndCandidates(signers, keys); d->ensureWizardVisible(); } void SignEMailController::setDetachedSignature(bool detached) { kleo_assert(!d->openpgp); kleo_assert(!d->cms); kleo_assert(d->completed.empty()); kleo_assert(d->runnable.empty()); d->detached = detached; } void SignEMailController::Private::slotWizardSignersResolved() { Q_EMIT q->signersResolved(); } // ### extract to base void SignEMailController::Private::slotWizardCanceled() { q->setLastError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel")); q->emitDoneOrError(); } void SignEMailController::setInputAndOutput(const std::shared_ptr<Input> &input, const std::shared_ptr<Output> &output) { setInputsAndOutputs(std::vector< std::shared_ptr<Input> >(1, input), std::vector< std::shared_ptr<Output> >(1, output)); } // ### extract to base void SignEMailController::setInputsAndOutputs(const std::vector< std::shared_ptr<Input> > &inputs, const std::vector< std::shared_ptr<Output> > &outputs) { kleo_assert(!inputs.empty()); kleo_assert(!outputs.empty()); std::vector< std::shared_ptr<SignEMailTask> > tasks; tasks.reserve(inputs.size()); d->ensureWizardCreated(); const std::vector<Key> keys = d->wizard->resolvedSigners(); kleo_assert(!keys.empty()); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr<SignEMailTask> task(new SignEMailTask); task->setInput(inputs[i]); task->setOutput(outputs[i]); task->setSigners(keys); task->setDetachedSignature(d->detached); if (d->mode == ClipboardMode) { if (d->protocol == OpenPGP) { task->setClearsign(true); } else { task->setAsciiArmor(true); } } tasks.push_back(task); } d->runnable.swap(tasks); } // ### extract to base void SignEMailController::start() { std::shared_ptr<TaskCollection> coll(new TaskCollection); std::vector<std::shared_ptr<Task> > tmp; std::copy(d->runnable.begin(), d->runnable.end(), std::back_inserter(tmp)); coll->setTasks(tmp); d->ensureWizardCreated(); d->wizard->setTaskCollection(coll); - Q_FOREACH (const std::shared_ptr<Task> &t, tmp) { + for (const std::shared_ptr<Task> &t : qAsConst(tmp)) { connectTask(t); } d->schedule(); } // ### extract to base void SignEMailController::Private::schedule() { if (!cms) if (const std::shared_ptr<SignEMailTask> t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr<SignEMailTask> t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (!cms && !openpgp) { kleo_assert(runnable.empty()); QPointer<QObject> Q = q; Q_FOREACH (const std::shared_ptr<SignEMailTask> t, completed) { Q_EMIT q->reportMicAlg(t->micAlg()); if (!Q) { return; } } q->emitDoneOrError(); } } // ### extract to base std::shared_ptr<SignEMailTask> SignEMailController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr<Task> &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr<SignEMailTask>(); } const std::shared_ptr<SignEMailTask> result = *it; runnable.erase(it); return result; } // ### extract to base void SignEMailController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result) { Q_UNUSED(result) Q_ASSERT(task); // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container if (task == d->cms.get()) { d->completed.push_back(d->cms); d->cms.reset(); } else if (task == d->openpgp.get()) { d->completed.push_back(d->openpgp); d->openpgp.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } // ### extract to base void SignEMailController::cancel() { try { if (d->wizard) { d->wizard->close(); } d->cancelAllJobs(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } // ### extract to base void SignEMailController::Private::cancelAllJobs() { // we just kill all runnable tasks - this will not result in // signal emissions. runnable.clear(); // a cancel() will result in a call to if (cms) { cms->cancel(); } if (openpgp) { openpgp->cancel(); } } // ### extract to base void SignEMailController::Private::ensureWizardCreated() { if (wizard) { return; } std::unique_ptr<SignEMailWizard> w(new SignEMailWizard); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(signersResolved()), q, SLOT(slotWizardSignersResolved()), Qt::QueuedConnection); connect(w.get(), SIGNAL(canceled()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); w->setPresetProtocol(protocol); EMailOperationsPreferences prefs; w->setQuickMode(prefs.quickSignEMail()); wizard = w.release(); } // ### extract to base void SignEMailController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(wizard); } #include "moc_signemailcontroller.cpp" diff --git a/src/crypto/verifychecksumscontroller.cpp b/src/crypto/verifychecksumscontroller.cpp index 717fc01dc..51a82f79d 100644 --- a/src/crypto/verifychecksumscontroller.cpp +++ b/src/crypto/verifychecksumscontroller.cpp @@ -1,685 +1,689 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/verifychecksumscontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "verifychecksumscontroller.h" #ifndef QT_NO_DIRMODEL #include <crypto/gui/verifychecksumsdialog.h> #include <utils/input.h> #include <utils/output.h> #include <utils/kleo_assert.h> #include <Libkleo/Stl_Util> #include <Libkleo/ChecksumDefinition> #include <Libkleo/Classify> #include <KLocalizedString> #include "kleopatra_debug.h" #include <QPointer> #include <QFileInfo> #include <QThread> #include <QMutex> #include <QProgressDialog> #include <QDir> #include <QProcess> #include <gpg-error.h> #include <deque> #include <limits> #include <set> using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; #ifdef Q_OS_UNIX static const bool HAVE_UNIX = true; #else static const bool HAVE_UNIX = false; #endif static const QLatin1String CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id"); static const Qt::CaseSensitivity fs_cs = HAVE_UNIX ? Qt::CaseSensitive : Qt::CaseInsensitive; // can we use QAbstractFileEngine::caseSensitive()? #if 0 static QStringList fs_sort(QStringList l) { int (*QString_compare)(const QString &, const QString &, Qt::CaseSensitivity) = &QString::compare; std::sort(l.begin(), l.end(), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, fs_cs) < 0; }); return l; } static QStringList fs_intersect(QStringList l1, QStringList l2) { int (*QString_compare)(const QString &, const QString &, Qt::CaseSensitivity) = &QString::compare; fs_sort(l1); fs_sort(l2); QStringList result; std::set_intersection(l1.begin(), l1.end(), l2.begin(), l2.end(), std::back_inserter(result), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, fs_cs) < 0; }); return result; } #endif static QList<QRegExp> get_patterns(const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions) { QList<QRegExp> result; for (const std::shared_ptr<ChecksumDefinition> &cd : checksumDefinitions) - if (cd) - Q_FOREACH (const QString &pattern, cd->patterns()) { + if (cd) { + const auto patterns = cd->patterns(); + for (const QString &pattern : patterns) { result.push_back(QRegExp(pattern, fs_cs)); } + } return result; } namespace { struct matches_any : std::unary_function<QString, bool> { const QList<QRegExp> m_regexps; explicit matches_any(const QList<QRegExp> ®exps) : m_regexps(regexps) {} bool operator()(const QString &s) const { return std::any_of(m_regexps.cbegin(), m_regexps.cend(), [&s](const QRegExp &rx) { return rx.exactMatch(s); }); } }; struct matches_none_of : std::unary_function<QString, bool> { const QList<QRegExp> m_regexps; explicit matches_none_of(const QList<QRegExp> ®exps) : m_regexps(regexps) {} bool operator()(const QString &s) const { return std::none_of(m_regexps.cbegin(), m_regexps.cend(), [&s](const QRegExp &rx) { return rx.exactMatch(s); }); } }; } class VerifyChecksumsController::Private : public QThread { Q_OBJECT friend class ::Kleo::Crypto::VerifyChecksumsController; VerifyChecksumsController *const q; public: explicit Private(VerifyChecksumsController *qq); ~Private() override; Q_SIGNALS: void baseDirectories(const QStringList &); void progress(int, int, const QString &); void status(const QString &file, Kleo::Crypto::Gui::VerifyChecksumsDialog::Status); private: void slotOperationFinished() { if (dialog) { dialog->setProgress(100, 100); dialog->setErrors(errors); } if (!errors.empty()) q->setLastError(gpg_error(GPG_ERR_GENERAL), errors.join(QLatin1Char('\n'))); q->emitDoneOrError(); } private: void run() override; private: QPointer<VerifyChecksumsDialog> dialog; mutable QMutex mutex; const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions; QStringList files; QStringList errors; volatile bool canceled; }; VerifyChecksumsController::Private::Private(VerifyChecksumsController *qq) : q(qq), dialog(), mutex(), checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()), files(), errors(), canceled(false) { connect(this, &Private::progress, q, &Controller::progress); connect(this, SIGNAL(finished()), q, SLOT(slotOperationFinished())); } VerifyChecksumsController::Private::~Private() { qCDebug(KLEOPATRA_LOG); } VerifyChecksumsController::VerifyChecksumsController(QObject *p) : Controller(p), d(new Private(this)) { } VerifyChecksumsController::VerifyChecksumsController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *p) : Controller(ctx, p), d(new Private(this)) { } VerifyChecksumsController::~VerifyChecksumsController() { qCDebug(KLEOPATRA_LOG); } void VerifyChecksumsController::setFiles(const QStringList &files) { kleo_assert(!d->isRunning()); kleo_assert(!files.empty()); const QMutexLocker locker(&d->mutex); d->files = files; } void VerifyChecksumsController::start() { { const QMutexLocker locker(&d->mutex); d->dialog = new VerifyChecksumsDialog; d->dialog->setAttribute(Qt::WA_DeleteOnClose); d->dialog->setWindowTitle(i18nc("@title:window", "Verify Checksum Results")); connect(d->dialog.data(), &VerifyChecksumsDialog::canceled, this, &VerifyChecksumsController::cancel); connect(d.get(), &Private::baseDirectories, d->dialog.data(), &VerifyChecksumsDialog::setBaseDirectories); connect(d.get(), &Private::progress, d->dialog.data(), &VerifyChecksumsDialog::setProgress); connect(d.get(), &Private::status, d->dialog.data(), &VerifyChecksumsDialog::setStatus); d->canceled = false; d->errors.clear(); } d->start(); d->dialog->show(); } void VerifyChecksumsController::cancel() { qCDebug(KLEOPATRA_LOG); const QMutexLocker locker(&d->mutex); d->canceled = true; } namespace { struct SumFile { QDir dir; QString sumFile; quint64 totalSize; std::shared_ptr<ChecksumDefinition> checksumDefinition; }; } static QStringList filter_checksum_files(QStringList l, const QList<QRegExp> &rxs) { l.erase(std::remove_if(l.begin(), l.end(), matches_none_of(rxs)), l.end()); return l; } namespace { struct File { QString name; QByteArray checksum; bool binary; }; } static QString decode(const QString &encoded) { QString decoded; decoded.reserve(encoded.size()); bool shift = false; for (const QChar &ch : encoded) if (shift) { switch (ch.toLatin1()) { case '\\': decoded += QLatin1Char('\\'); break; case 'n': decoded += QLatin1Char('\n'); break; default: qCDebug(KLEOPATRA_LOG) << "invalid escape sequence" << '\\' << ch << "(interpreted as '" << ch << "')"; decoded += ch; break; } shift = false; } else { if (ch == QLatin1Char('\\')) { shift = true; } else { decoded += ch; } } return decoded; } static std::vector<File> parse_sum_file(const QString &fileName) { std::vector<File> files; QFile f(fileName); if (f.open(QIODevice::ReadOnly)) { QTextStream s(&f); QRegExp rx(QLatin1String("(\\?)([a-f0-9A-F]+) ([ *])([^\n]+)\n*")); while (!s.atEnd()) { const QString line = s.readLine(); if (rx.exactMatch(line)) { Q_ASSERT(!rx.cap(4).endsWith(QLatin1Char('\n'))); const File file = { rx.cap(1) == QLatin1String("\\") ? decode(rx.cap(4)) : rx.cap(4), rx.cap(2).toLatin1(), rx.cap(3) == QLatin1String("*"), }; files.push_back(file); } } } return files; } static quint64 aggregate_size(const QDir &dir, const QStringList &files) { quint64 n = 0; for (const QString &file : files) { n += QFileInfo(dir.absoluteFilePath(file)).size(); } return n; } static std::shared_ptr<ChecksumDefinition> filename2definition(const QString &fileName, const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions) { for (const std::shared_ptr<ChecksumDefinition> &cd : checksumDefinitions) - if (cd) - Q_FOREACH (const QString &pattern, cd->patterns()) + if (cd) { + const auto patterns = cd->patterns(); + for (const QString &pattern : patterns) if (QRegExp(pattern, fs_cs).exactMatch(fileName)) { return cd; } + } return std::shared_ptr<ChecksumDefinition>(); } namespace { struct less_dir : std::binary_function<QDir, QDir, bool> { bool operator()(const QDir &lhs, const QDir &rhs) const { return QString::compare(lhs.absolutePath(), rhs.absolutePath(), fs_cs) < 0; } }; struct less_file : std::binary_function<QString, QString, bool> { bool operator()(const QString &lhs, const QString &rhs) const { return QString::compare(lhs, rhs, fs_cs) < 0; } }; struct sumfile_contains_file : std::unary_function<QString, bool> { const QDir dir; const QString fileName; sumfile_contains_file(const QDir &dir_, const QString &fileName_) : dir(dir_), fileName(fileName_) {} bool operator()(const QString &sumFile) const { const std::vector<File> files = parse_sum_file(dir.absoluteFilePath(sumFile)); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << files.size() << " files listed in " << qPrintable(dir.absoluteFilePath(sumFile)); for (const File &file : files) { const bool isSameFileName = (QString::compare(file.name, fileName, fs_cs) == 0); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: " << qPrintable(file.name) << " == " << qPrintable(fileName) << " ? " << isSameFileName; if (isSameFileName) { return true; } } return false; } }; } // IF is_dir(file) // add all sumfiles \in dir(file) // inputs.prepend( all dirs \in dir(file) ) // ELSE IF is_sum_file(file) // add // ELSE IF \exists sumfile in dir(file) \where sumfile \contains file // add sumfile // ELSE // error: no checksum found for "file" static QStringList find_base_directories(const QStringList &files) { // Step 1: find base dirs: std::set<QDir, less_dir> dirs; for (const QString &file : files) { const QFileInfo fi(file); const QDir dir = fi.isDir() ? QDir(file) : fi.dir(); dirs.insert(dir); } // Step 1a: collapse direct child directories bool changed; do { changed = false; std::set<QDir, less_dir>::iterator it = dirs.begin(); while (it != dirs.end()) { QDir dir = *it; if (dir.cdUp() && dirs.count(dir)) { dirs.erase(it++); changed = true; } else { ++it; } } } while (changed); QStringList rv; rv.reserve(dirs.size()); std::transform(dirs.cbegin(), dirs.cend(), std::back_inserter(rv), std::mem_fn(&QDir::absolutePath)); return rv; } static std::vector<SumFile> find_sums_by_input_files(const QStringList &files, QStringList &errors, const std::function<void(int)> &progress, const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions) { const QList<QRegExp> patterns = get_patterns(checksumDefinitions); const matches_any is_sum_file(patterns); std::map<QDir, std::set<QString, less_file>, less_dir> dirs2sums; // Step 1: find the sumfiles we need to check: std::deque<QString> inputs(files.begin(), files.end()); int i = 0; while (!inputs.empty()) { const QString file = inputs.front(); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: considering " << qPrintable(file); inputs.pop_front(); const QFileInfo fi(file); const QString fileName = fi.fileName(); if (fi.isDir()) { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's a directory"; QDir dir(file); const QStringList sumfiles = filter_checksum_files(dir.entryList(QDir::Files), patterns); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << sumfiles.size() << " sum files: " << qPrintable(sumfiles.join(QLatin1String(", "))); dirs2sums[ dir ].insert(sumfiles.begin(), sumfiles.end()); const QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << dirs.size() << " subdirs, prepending"; std::transform(dirs.cbegin(), dirs.cend(), std::inserter(inputs, inputs.begin()), [&dir](const QString &path) { return dir.absoluteFilePath(path); }); } else if (is_sum_file(fileName)) { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's a sum file"; dirs2sums[fi.dir()].insert(fileName); } else { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's something else; checking whether we'll find a sumfile for it..."; const QDir dir = fi.dir(); const QStringList sumfiles = filter_checksum_files(dir.entryList(QDir::Files), patterns); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << sumfiles.size() << " potential sumfiles: " << qPrintable(sumfiles.join(QLatin1String(", "))); const auto it = std::find_if(sumfiles.cbegin(), sumfiles.cend(), sumfile_contains_file(dir, fileName)); if (it == sumfiles.end()) { errors.push_back(i18n("Cannot find checksums file for file %1", file)); } else { dirs2sums[dir].insert(*it); } } if (progress) { progress(++i); } } // Step 2: convert into vector<SumFile>: std::vector<SumFile> sumfiles; sumfiles.reserve(dirs2sums.size()); for (std::map<QDir, std::set<QString, less_file>, less_dir>::const_iterator it = dirs2sums.begin(), end = dirs2sums.end(); it != end; ++it) { if (it->second.empty()) { continue; } const QDir &dir = it->first; Q_FOREACH (const QString &sumFileName, it->second) { const std::vector<File> summedfiles = parse_sum_file(dir.absoluteFilePath(sumFileName)); QStringList files; files.reserve(summedfiles.size()); std::transform(summedfiles.cbegin(), summedfiles.cend(), std::back_inserter(files), std::mem_fn(&File::name)); const SumFile sumFile = { it->first, sumFileName, aggregate_size(it->first, files), filename2definition(sumFileName, checksumDefinitions), }; sumfiles.push_back(sumFile); } if (progress) { progress(++i); } } return sumfiles; } static QStringList c_lang_environment() { QStringList env = QProcess::systemEnvironment(); env.erase(std::remove_if(env.begin(), env.end(), [](const QString &str) { return QRegExp(QLatin1String("^LANG=.*"), fs_cs).exactMatch(str); }), env.end()); env.push_back(QStringLiteral("LANG=C")); return env; } static const struct { const char *string; VerifyChecksumsDialog::Status status; } statusStrings[] = { { "OK", VerifyChecksumsDialog::OK }, { "FAILED", VerifyChecksumsDialog::Failed }, }; static const size_t numStatusStrings = sizeof statusStrings / sizeof * statusStrings; static VerifyChecksumsDialog::Status string2status(const QByteArray &str) { for (unsigned int i = 0; i < numStatusStrings; ++i) if (str == statusStrings[i].string) { return statusStrings[i].status; } return VerifyChecksumsDialog::Unknown; } static QString process(const SumFile &sumFile, bool *fatal, const QStringList &env, const std::function<void(const QString &, VerifyChecksumsDialog::Status)> &status) { QProcess p; p.setEnvironment(env); p.setWorkingDirectory(sumFile.dir.absolutePath()); p.setReadChannel(QProcess::StandardOutput); const QString absFilePath = sumFile.dir.absoluteFilePath(sumFile.sumFile); const QString program = sumFile.checksumDefinition->verifyCommand(); sumFile.checksumDefinition->startVerifyCommand(&p, QStringList(absFilePath)); QByteArray remainder; // used for filenames with newlines in them while (p.state() != QProcess::NotRunning) { p.waitForReadyRead(); while (p.canReadLine()) { const QByteArray line = p.readLine(); const int colonIdx = line.lastIndexOf(':'); if (colonIdx < 0) { remainder += line; // no colon -> probably filename with a newline continue; } const QString file = QFile::decodeName(remainder + line.left(colonIdx)); remainder.clear(); const VerifyChecksumsDialog::Status result = string2status(line.mid(colonIdx + 1).trimmed()); status(sumFile.dir.absoluteFilePath(file), result); } } qCDebug(KLEOPATRA_LOG) << "[" << &p << "] Exit code " << p.exitCode(); if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) { if (fatal && p.error() == QProcess::FailedToStart) { *fatal = true; } if (p.error() == QProcess::UnknownError) return i18n("Error while running %1: %2", program, QString::fromLocal8Bit(p.readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", program, p.errorString()); } } return QString(); } namespace { static QDebug operator<<(QDebug s, const SumFile &sum) { return s << "SumFile(" << sum.dir << "->" << sum.sumFile << "<-(" << sum.totalSize << ')' << ")\n"; } } void VerifyChecksumsController::Private::run() { QMutexLocker locker(&mutex); const QStringList files = this->files; const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions = this->checksumDefinitions; locker.unlock(); QStringList errors; // // Step 0: find base directories: // Q_EMIT baseDirectories(find_base_directories(files)); // // Step 1: build a list of work to do (no progress): // const QString scanning = i18n("Scanning directories..."); Q_EMIT progress(0, 0, scanning); const auto progressCb = [this, scanning](int arg) { Q_EMIT progress(arg, 0, scanning); }; const auto statusCb = [this](const QString &str, VerifyChecksumsDialog::Status st) { Q_EMIT status(str, st); }; const std::vector<SumFile> sumfiles = find_sums_by_input_files(files, errors, progressCb, checksumDefinitions); for (const SumFile &sumfile : sumfiles) { qCDebug(KLEOPATRA_LOG) << sumfile; } if (!canceled) { Q_EMIT progress(0, 0, i18n("Calculating total size...")); const quint64 total = kdtools::accumulate_transform(sumfiles.cbegin(), sumfiles.cend(), std::mem_fn(&SumFile::totalSize), Q_UINT64_C(0)); if (!canceled) { // // Step 2: perform work (with progress reporting): // const QStringList env = c_lang_environment(); // re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...) const quint64 factor = total / std::numeric_limits<int>::max() + 1; quint64 done = 0; Q_FOREACH (const SumFile &sumFile, sumfiles) { Q_EMIT progress(done / factor, total / factor, i18n("Verifying checksums (%2) in %1", sumFile.checksumDefinition->label(), sumFile.dir.path())); bool fatal = false; const QString error = process(sumFile, &fatal, env, statusCb); if (!error.isEmpty()) { errors.push_back(error); } done += sumFile.totalSize; if (fatal || canceled) { break; } } Q_EMIT progress(done / factor, total / factor, i18n("Done.")); } } locker.relock(); this->errors = errors; // mutex unlocked by QMutexLocker } #include "moc_verifychecksumscontroller.cpp" #include "verifychecksumscontroller.moc" #endif // QT_NO_DIRMODEL diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp index bd114520c..2525bf917 100644 --- a/src/kleopatraapplication.cpp +++ b/src/kleopatraapplication.cpp @@ -1,646 +1,647 @@ /* kleopatraapplication.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-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "kleopatraapplication.h" #include "mainwindow.h" #include "kleopatra_options.h" #include "systrayicon.h" #include <smartcard/readerstatus.h> #include <conf/configuredialog.h> #include <Libkleo/GnuPG> #include <utils/kdpipeiodevice.h> #include <utils/log.h> #include <gpgme++/key.h> #include <Libkleo/FileSystemWatcher> #include <Libkleo/KeyCache> #include <Libkleo/Classify> #ifdef HAVE_USABLE_ASSUAN # include <uiserver/uiserver.h> #endif #include "commands/signencryptfilescommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/checksumcreatefilescommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/detailscommand.h" #include "commands/newcertificatecommand.h" #include "dialogs/updatenotification.h" #include <KIconLoader> #include <KLocalizedString> #include "kleopatra_debug.h" #include <KMessageBox> #include <KWindowSystem> #include <QFile> #include <QDir> #include <QPointer> #include <memory> #include <KSharedConfig> using namespace Kleo; using namespace Kleo::Commands; static void add_resources() { KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra")); KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg")); } static QList<QByteArray> default_logging_options() { QList<QByteArray> result; result.push_back("io"); return result; } class KleopatraApplication::Private { friend class ::KleopatraApplication; KleopatraApplication *const q; public: explicit Private(KleopatraApplication *qq) : q(qq), ignoreNewInstance(true), firstNewInstance(true), sysTray(nullptr) { } ~Private() { #ifndef QT_NO_SYSTEMTRAYICON delete sysTray; #endif } void init() { KDAB_SET_OBJECT_NAME(readerStatus); #ifndef QT_NO_SYSTEMTRAYICON sysTray = new SysTrayIcon(); sysTray->setFirstCardWithNullPin(readerStatus.firstCardWithNullPin()); sysTray->setAnyCardCanLearnKeys(readerStatus.anyCardCanLearnKeys()); connect(&readerStatus, &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin); connect(&readerStatus, &SmartCard::ReaderStatus::anyCardCanLearnKeysChanged, sysTray, &SysTrayIcon::setAnyCardCanLearnKeys); #endif } private: void connectConfigureDialog() { if (configureDialog && q->mainWindow()) { connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); } } void disconnectConfigureDialog() { if (configureDialog && q->mainWindow()) { disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); } } public: bool ignoreNewInstance; bool firstNewInstance; QPointer<ConfigureDialog> configureDialog; QPointer<MainWindow> mainWindow; SmartCard::ReaderStatus readerStatus; #ifndef QT_NO_SYSTEMTRAYICON SysTrayIcon *sysTray; #endif std::shared_ptr<KeyCache> keyCache; std::shared_ptr<Log> log; std::shared_ptr<FileSystemWatcher> watcher; public: void setupKeyCache() { keyCache = KeyCache::mutableInstance(); watcher.reset(new FileSystemWatcher); watcher->whitelistFiles(gnupgFileWhitelist()); watcher->addPath(gnupgHomeDirectory()); watcher->setDelay(1000); keyCache->addFileSystemWatcher(watcher); } void setupLogging() { log = Log::mutableInstance(); const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS"); const bool logAll = envOptions.trimmed() == "all"; const QList<QByteArray> options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(','); const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR"); if (dirNative.isEmpty()) { return; } const QString dir = QFile::decodeName(dirNative); const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid())); std::unique_ptr<QFile> logFile(new QFile(logFileName)); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) { qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled"; return; } log->setOutputDirectory(dir); if (logAll || options.contains("io")) { log->setIOLoggingEnabled(true); } qInstallMessageHandler(Log::messageHandler); #ifdef HAVE_USABLE_ASSUAN if (logAll || options.contains("pipeio")) { KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug); } UiServer::setLogStream(log->logFile()); #endif } }; KleopatraApplication::KleopatraApplication(int &argc, char *argv[]) : QApplication(argc, argv), d(new Private(this)) { } void KleopatraApplication::init() { d->init(); add_resources(); d->setupKeyCache(); d->setupLogging(); #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->show(); #endif setQuitOnLastWindowClosed(false); KWindowSystem::allowExternalProcessWindowActivation(); } KleopatraApplication::~KleopatraApplication() { // main window doesn't receive "close" signal and cannot // save settings before app exit delete d->mainWindow; // work around kdelibs bug https://bugs.kde.org/show_bug.cgi?id=162514 KSharedConfig::openConfig()->sync(); } namespace { typedef void (KleopatraApplication::*Func)(const QStringList &, GpgME::Protocol); } void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory) { QCommandLineParser parser; kleopatra_options(&parser); QString err; if (!arguments.isEmpty() && !parser.parse(arguments)) { err = parser.errorText(); } else if (arguments.isEmpty()) { // KDBusServices omits the application name if no other // arguments are provided. In that case the parser prints // a warning. parser.parse(QStringList() << QCoreApplication::applicationFilePath()); } if (err.isEmpty()) { err = newInstance(parser, workingDirectory); } if (!err.isEmpty()) { KMessageBox::sorry(nullptr, err.toHtmlEscaped(), i18n("Failed to execute command")); Q_EMIT setExitValue(1); return; } Q_EMIT setExitValue(0); } QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory) { if (d->ignoreNewInstance) { qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance"; return QString(); } QStringList files; const QDir cwd = QDir(workingDirectory); bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search")); // Query and Search treat positional arguments differently, see below. if (!queryMode) { - Q_FOREACH (const QString &file, parser.positionalArguments()) { + const auto positionalArguments = parser.positionalArguments(); + for (const QString &file : positionalArguments) { // We do not check that file exists here. Better handle // these errors in the UI. if (QFileInfo(file).isAbsolute()) { files << file; } else { files << cwd.absoluteFilePath(file); } } } GpgME::Protocol protocol = GpgME::UnknownProtocol; if (parser.isSet(QStringLiteral("openpgp"))) { qCDebug(KLEOPATRA_LOG) << "found OpenPGP"; protocol = GpgME::OpenPGP; } if (parser.isSet(QStringLiteral("cms"))) { qCDebug(KLEOPATRA_LOG) << "found CMS"; if (protocol == GpgME::OpenPGP) { return i18n("Ambiguous protocol: --openpgp and --cms"); } protocol = GpgME::CMS; } // Check for Parent Window id WId parentId = 0; if (parser.isSet(QStringLiteral("parent-windowid"))) { #ifdef Q_OS_WIN // WId is not a portable type as it is a pointer type on Windows. // casting it from an integer is ok though as the values are guaranteed to // be compatible in the documentation. parentId = reinterpret_cast<WId>(parser.value(QStringLiteral("parent-windowid")).toUInt()); #else parentId = parser.value(QStringLiteral("parent-windowid")).toUInt(); #endif } // Handle openpgp4fpr URI scheme QString needle; if (queryMode) { needle = parser.positionalArguments().join(QLatin1Char(' ')); } if (needle.startsWith(QLatin1String("openpgp4fpr:"))) { needle.remove(0, 12); } // Check for --search command. if (parser.isSet(QStringLiteral("search"))) { // This is an extra command instead of a combination with the // similar query to avoid changing the older query commands behavior // and query's "show details if a certificate exist or search on a // keyserver" logic is hard to explain and use consistently. if (needle.isEmpty()) { return i18n("No search string specified for --search"); } LookupCertificatesCommand *const cmd = new LookupCertificatesCommand(needle, nullptr); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --query command if (parser.isSet(QStringLiteral("query"))) { if (needle.isEmpty()) { return i18n("No fingerprint argument specified for --query"); } auto cmd = Command::commandForQuery(needle); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --gen-key command if (parser.isSet(QStringLiteral("gen-key"))) { auto cmd = new NewCertificateCommand(nullptr); cmd->setParentWId(parentId); cmd->setProtocol(protocol); cmd->start(); return QString(); } // Check for --config command if (parser.isSet(QStringLiteral("config"))) { openConfigDialogWithForeignParent(parentId); return QString(); } static const QMap<QString, Func> funcMap { { QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile }, { QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles }, { QStringLiteral("sign"), &KleopatraApplication::signFiles }, { QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles }, { QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles }, { QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles }, { QStringLiteral("verify"), &KleopatraApplication::verifyFiles }, { QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles }, { QStringLiteral("checksum"), &KleopatraApplication::checksumFiles }, }; QString found; Q_FOREACH (const QString &opt, funcMap.keys()) { if (parser.isSet(opt) && found.isEmpty()) { found = opt; } else if (parser.isSet(opt)) { return i18n("Ambiguous commands \"%1\" and \"%2\"", found, opt); } } QStringList errors; if (!found.isEmpty()) { if (files.empty()) { return i18n("No files specified for \"%1\" command", found); } qCDebug(KLEOPATRA_LOG) << "found" << found; (this->*funcMap.value(found))(files, protocol); } else { if (files.empty()) { if (!(d->firstNewInstance && isSessionRestored())) { qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow"; openOrRaiseMainWindow(); } } else { - Q_FOREACH (const QString& fileName, files) { + for (const QString& fileName : qAsConst(files)) { QFileInfo fi(fileName); if (!fi.isReadable()) { errors << i18n("Cannot read \"%1\"", fileName); } } Q_FOREACH (Command *cmd, Command::commandsForFiles(files)) { if (parentId) { cmd->setParentWId(parentId); } else { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } cmd->setParentWidget(mw); } cmd->start(); } } } d->firstNewInstance = false; #ifdef Q_OS_WIN // On Windows we might be started from the // explorer in any working directory. E.g. // a double click on a file. To avoid preventing // the folder from deletion we set the // working directory to the users homedir. QDir::setCurrent(QDir::homePath()); #endif return errors.join(QLatin1Char('\n')); } #ifndef QT_NO_SYSTEMTRAYICON const SysTrayIcon *KleopatraApplication::sysTrayIcon() const { return d->sysTray; } SysTrayIcon *KleopatraApplication::sysTrayIcon() { return d->sysTray; } #endif const MainWindow *KleopatraApplication::mainWindow() const { return d->mainWindow; } MainWindow *KleopatraApplication::mainWindow() { return d->mainWindow; } void KleopatraApplication::setMainWindow(MainWindow *mainWindow) { if (mainWindow == d->mainWindow) { return; } d->disconnectConfigureDialog(); d->mainWindow = mainWindow; #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->setMainWindow(mainWindow); #endif d->connectConfigureDialog(); } static void open_or_raise(QWidget *w) { if (w->isMinimized()) { KWindowSystem::unminimizeWindow(w->winId()); w->raise(); } else if (w->isVisible()) { w->raise(); } else { w->show(); } } void KleopatraApplication::toggleMainWindowVisibility() { if (mainWindow()) { mainWindow()->setVisible(!mainWindow()->isVisible()); } else { openOrRaiseMainWindow(); } } void KleopatraApplication::restoreMainWindow() { qCDebug(KLEOPATRA_LOG) << "restoring main window"; // Sanity checks if (!isSessionRestored()) { qCDebug(KLEOPATRA_LOG) << "Not in session restore"; return; } if (mainWindow()) { qCDebug(KLEOPATRA_LOG) << "Already have main window"; return; } MainWindow *mw = new MainWindow; if (KMainWindow::canBeRestored(1)) { // restore to hidden state, Mainwindow::readProperties() will // restore saved visibility. mw->restore(1, false); } mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } void KleopatraApplication::openOrRaiseMainWindow() { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } open_or_raise(mw); UpdateNotification::checkUpdate(mw); } void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId) { if (!d->configureDialog) { d->configureDialog = new ConfigureDialog; d->configureDialog->setAttribute(Qt::WA_DeleteOnClose); d->connectConfigureDialog(); } // This is similar to what the commands do. if (parentWId) { if (QWidget *pw = QWidget::find(parentWId)) { d->configureDialog->setParent(pw, d->configureDialog->windowFlags()); } else { d->configureDialog->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId); } } open_or_raise(d->configureDialog); // If we have a parent we want to raise over it. if (parentWId) { d->configureDialog->raise(); } } void KleopatraApplication::openOrRaiseConfigDialog() { openConfigDialogWithForeignParent(0); } #ifndef QT_NO_SYSTEMTRAYICON void KleopatraApplication::startMonitoringSmartCard() { d->readerStatus.startMonitoring(); } #endif // QT_NO_SYSTEMTRAYICON void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/) { openOrRaiseMainWindow(); if (!files.empty()) { mainWindow()->importCertificatesFromFile(files); } } void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto) { SignEncryptFilesCommand *const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setEncryptionPolicy(Force); cmd->setSigningPolicy(Allow); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto) { SignEncryptFilesCommand *const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setSigningPolicy(Force); cmd->setEncryptionPolicy(Deny); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto) { SignEncryptFilesCommand *const cmd = new SignEncryptFilesCommand(files, nullptr); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/) { DecryptVerifyFilesCommand *const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Decrypt); cmd->start(); } void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { DecryptVerifyFilesCommand *const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Verify); cmd->start(); } void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { DecryptVerifyFilesCommand *const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->start(); } void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/) { QStringList verifyFiles, createFiles; for (const QString &file : files) { if (isChecksumFile(file)) { verifyFiles << file; } else { createFiles << file; } } if (!verifyFiles.isEmpty()) { auto *const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr); cmd->start(); } if (!createFiles.isEmpty()) { auto *const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr); cmd->start(); } } void KleopatraApplication::setIgnoreNewInstance(bool ignore) { d->ignoreNewInstance = ignore; } bool KleopatraApplication::ignoreNewInstance() const { return d->ignoreNewInstance; } diff --git a/src/newcertificatewizard/newcertificatewizard.cpp b/src/newcertificatewizard/newcertificatewizard.cpp index 1ebdbf2be..fe7c24063 100644 --- a/src/newcertificatewizard/newcertificatewizard.cpp +++ b/src/newcertificatewizard/newcertificatewizard.cpp @@ -1,1913 +1,1913 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/newcertificatewizard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include "newcertificatewizard.h" #include "ui_chooseprotocolpage.h" #include "ui_enterdetailspage.h" #include "ui_keycreationpage.h" #include "ui_resultpage.h" #include "ui_advancedsettingsdialog.h" #include "commands/exportsecretkeycommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #include "commands/exportcertificatecommand.h" #include "kleopatraapplication.h" #include "utils/validation.h" #include "utils/filedialog.h" #include "utils/keyparameters.h" #include "utils/userinfo.h" #include <Libkleo/GnuPG> #include <Libkleo/Stl_Util> #include <Libkleo/Dn> #include <Libkleo/OidMap> #include <Libkleo/KeyCache> #include <Libkleo/Formatting> #include <QGpgME/KeyGenerationJob> #include <QGpgME/Protocol> #include <QGpgME/CryptoConfig> #include <gpgme++/global.h> #include <gpgme++/keygenerationresult.h> #include <gpgme++/context.h> #include <gpgme++/interfaces/passphraseprovider.h> #include <KConfigGroup> #include <KLocalizedString> #include "kleopatra_debug.h" #include <QTemporaryDir> #include <KMessageBox> #include <QIcon> #include <QRegExpValidator> #include <QLineEdit> #include <QMetaProperty> #include <QDir> #include <QFile> #include <QUrl> #include <QDesktopServices> #include <QUrlQuery> #include <algorithm> #include <KSharedConfig> #include <QLocale> using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace Kleo::Commands; using namespace GpgME; static const char RSA_KEYSIZES_ENTRY[] = "RSAKeySizes"; static const char DSA_KEYSIZES_ENTRY[] = "DSAKeySizes"; static const char ELG_KEYSIZES_ENTRY[] = "ELGKeySizes"; static const char RSA_KEYSIZE_LABELS_ENTRY[] = "RSAKeySizeLabels"; static const char DSA_KEYSIZE_LABELS_ENTRY[] = "DSAKeySizeLabels"; static const char ELG_KEYSIZE_LABELS_ENTRY[] = "ELGKeySizeLabels"; static const char PGP_KEY_TYPE_ENTRY[] = "PGPKeyType"; static const char CMS_KEY_TYPE_ENTRY[] = "CMSKeyType"; // This should come from gpgme in the future // For now we only support the basic 2.1 curves and check // for GnuPG 2.1. The whole subkey / usage generation needs // new api and a reworked dialog. (ah 10.3.16) // EDDSA should be supported, too. static const QStringList curveNames { { QStringLiteral("brainpoolP256r1") }, { QStringLiteral("brainpoolP384r1") }, { QStringLiteral("brainpoolP512r1") }, { QStringLiteral("NIST P-256") }, { QStringLiteral("NIST P-384") }, { QStringLiteral("NIST P-521") }, }; class EmptyPassphraseProvider: public PassphraseProvider { public: char *getPassphrase(const char * /*useridHint*/, const char * /*description*/, bool /*previousWasBad*/, bool &/*canceled*/) Q_DECL_OVERRIDE { return gpgrt_strdup (""); } }; static void set_tab_order(const QList<QWidget *> &wl) { kdtools::for_each_adjacent_pair(wl.begin(), wl.end(), &QWidget::setTabOrder); } enum KeyAlgo { RSA, DSA, ELG, ECDSA, ECDH, EDDSA }; static bool is_algo(Subkey::PubkeyAlgo algo, KeyAlgo what) { switch (algo) { case Subkey::AlgoRSA: case Subkey::AlgoRSA_E: case Subkey::AlgoRSA_S: return what == RSA; case Subkey::AlgoELG_E: case Subkey::AlgoELG: return what == ELG; case Subkey::AlgoDSA: return what == DSA; case Subkey::AlgoECDSA: return what == ECDSA; case Subkey::AlgoECDH: return what == ECDH; case Subkey::AlgoEDDSA: return what == EDDSA; default: break; } return false; } static bool is_rsa(unsigned int algo) { return is_algo(static_cast<Subkey::PubkeyAlgo>(algo), RSA); } static bool is_dsa(unsigned int algo) { return is_algo(static_cast<Subkey::PubkeyAlgo>(algo), DSA); } static bool is_elg(unsigned int algo) { return is_algo(static_cast<Subkey::PubkeyAlgo>(algo), ELG); } static bool is_ecdsa(unsigned int algo) { return is_algo(static_cast<Subkey::PubkeyAlgo>(algo), ECDSA); } static bool is_eddsa(unsigned int algo) { return is_algo(static_cast<Subkey::PubkeyAlgo>(algo), EDDSA); } static bool is_ecdh(unsigned int algo) { return is_algo(static_cast<Subkey::PubkeyAlgo>(algo), ECDH); } static void force_set_checked(QAbstractButton *b, bool on) { // work around Qt bug (tested: 4.1.4, 4.2.3, 4.3.4) const bool autoExclusive = b->autoExclusive(); b->setAutoExclusive(false); b->setChecked(b->isEnabled() && on); b->setAutoExclusive(autoExclusive); } static void set_keysize(QComboBox *cb, unsigned int strength) { if (!cb) { return; } const int idx = cb->findData(static_cast<int>(strength)); cb->setCurrentIndex(idx); } static unsigned int get_keysize(const QComboBox *cb) { if (!cb) { return 0; } const int idx = cb->currentIndex(); if (idx < 0) { return 0; } return cb->itemData(idx).toInt(); } static void set_curve(QComboBox *cb, const QString &curve) { if (!cb) { return; } const int idx = cb->findText(curve); if (idx < 0) { // Can't happen as we don't have them configurable. qCWarning(KLEOPATRA_LOG) << "curve " << curve << " not allowed"; } cb->setCurrentIndex(idx); } static QString get_curve(const QComboBox *cb) { if (!cb) { return QString(); } return cb->currentText(); } // Extract the algo information from default_pubkey_algo format // // and put it into the return values size, algo and curve. // // Values look like: // RSA-2048 // rsa2048/cert,sign+rsa2048/enc // brainpoolP256r1+brainpoolP256r1 static void parseAlgoString(const QString &algoString, int *size, Subkey::PubkeyAlgo *algo, QString &curve) { const auto split = algoString.split(QLatin1Char('/')); bool isEncrypt = split.size() == 2 && split[1].contains(QLatin1String("enc")); // Normalize const auto lowered = split[0].toLower().remove(QLatin1Char('-')); if (!algo || !size) { return; } *algo = Subkey::AlgoUnknown; if (lowered.startsWith(QLatin1String("rsa"))) { *algo = Subkey::AlgoRSA; } else if (lowered.startsWith(QLatin1String("dsa"))) { *algo = Subkey::AlgoDSA; } else if (lowered.startsWith(QLatin1String("elg"))) { *algo = Subkey::AlgoELG; } if (*algo != Subkey::AlgoUnknown) { bool ok; *size = lowered.rightRef(lowered.size() - 3).toInt(&ok); if (!ok) { qCWarning(KLEOPATRA_LOG) << "Could not extract size from: " << lowered; *size = 3072; } return; } // Now the ECC Algorithms if (lowered.startsWith(QLatin1String("ed25519"))) { // Special handling for this as technically // this is a cv25519 curve used for EDDSA curve = split[0]; *algo = Subkey::AlgoEDDSA; return; } if (lowered.startsWith(QLatin1String("cv25519")) || lowered.startsWith(QLatin1String("nist")) || lowered.startsWith(QLatin1String("brainpool")) || lowered.startsWith(QLatin1String("secp"))) { curve = split[0]; *algo = isEncrypt ? Subkey::AlgoECDH : Subkey::AlgoECDSA; return; } qCWarning(KLEOPATRA_LOG) << "Failed to parse default_pubkey_algo:" << algoString; } Q_DECLARE_METATYPE(GpgME::Subkey::PubkeyAlgo) namespace Kleo { namespace NewCertificateUi { class WizardPage : public QWizardPage { Q_OBJECT protected: explicit WizardPage(QWidget *parent = nullptr) : QWizardPage(parent) {} NewCertificateWizard *wizard() const { Q_ASSERT(static_cast<NewCertificateWizard *>(QWizardPage::wizard()) == qobject_cast<NewCertificateWizard *>(QWizardPage::wizard())); return static_cast<NewCertificateWizard *>(QWizardPage::wizard()); } QAbstractButton *button(QWizard::WizardButton button) const { return QWizardPage::wizard() ? QWizardPage::wizard()->button(button) : nullptr; } bool isButtonVisible(QWizard::WizardButton button) const { if (const QAbstractButton *const b = this->button(button)) { return b->isVisible(); } else { return false; } } QDir tmpDir() const; protected Q_SLOTS: void setButtonVisible(QWizard::WizardButton button, bool visible) { if (QAbstractButton *const b = this->button(button)) { b->setVisible(visible); } } protected: #define FIELD(type, name) type name() const { return field( QStringLiteral(#name) ).value<type>(); } FIELD(bool, pgp) FIELD(bool, signingAllowed) FIELD(bool, encryptionAllowed) FIELD(bool, certificationAllowed) FIELD(bool, authenticationAllowed) FIELD(QString, name) FIELD(QString, email) FIELD(QString, dn) FIELD(bool, protectedKey) FIELD(Subkey::PubkeyAlgo, keyType) FIELD(int, keyStrength) FIELD(QString, keyCurve) FIELD(Subkey::PubkeyAlgo, subkeyType) FIELD(int, subkeyStrength) FIELD(QString, subkeyCurve) FIELD(QDate, expiryDate) FIELD(QStringList, additionalUserIDs) FIELD(QStringList, additionalEMailAddresses) FIELD(QStringList, dnsNames) FIELD(QStringList, uris) FIELD(QString, url) FIELD(QString, error) FIELD(QString, result) FIELD(QString, fingerprint) #undef FIELD }; } // namespace NewCertificateUi } // namespace Kleo using namespace Kleo::NewCertificateUi; namespace { class AdvancedSettingsDialog : public QDialog { Q_OBJECT Q_PROPERTY(QStringList additionalUserIDs READ additionalUserIDs WRITE setAdditionalUserIDs) Q_PROPERTY(QStringList additionalEMailAddresses READ additionalEMailAddresses WRITE setAdditionalEMailAddresses) Q_PROPERTY(QStringList dnsNames READ dnsNames WRITE setDnsNames) Q_PROPERTY(QStringList uris READ uris WRITE setUris) Q_PROPERTY(uint keyStrength READ keyStrength WRITE setKeyStrength) Q_PROPERTY(Subkey::PubkeyAlgo keyType READ keyType WRITE setKeyType) Q_PROPERTY(QString keyCurve READ keyCurve WRITE setKeyCurve) Q_PROPERTY(uint subkeyStrength READ subkeyStrength WRITE setSubkeyStrength) Q_PROPERTY(QString subkeyCurve READ subkeyCurve WRITE setSubkeyCurve) Q_PROPERTY(Subkey::PubkeyAlgo subkeyType READ subkeyType WRITE setSubkeyType) Q_PROPERTY(bool signingAllowed READ signingAllowed WRITE setSigningAllowed) Q_PROPERTY(bool encryptionAllowed READ encryptionAllowed WRITE setEncryptionAllowed) Q_PROPERTY(bool certificationAllowed READ certificationAllowed WRITE setCertificationAllowed) Q_PROPERTY(bool authenticationAllowed READ authenticationAllowed WRITE setAuthenticationAllowed) Q_PROPERTY(QDate expiryDate READ expiryDate WRITE setExpiryDate) public: explicit AdvancedSettingsDialog(QWidget *parent = nullptr) : QDialog(parent), protocol(UnknownProtocol), pgpDefaultAlgorithm(Subkey::AlgoELG_E), cmsDefaultAlgorithm(Subkey::AlgoRSA), keyTypeImmutable(false), ui(), mECCSupported(engineIsVersion(2, 1, 0)), mEdDSASupported(engineIsVersion(2, 1, 15)) { qRegisterMetaType<Subkey::PubkeyAlgo>("Subkey::PubkeyAlgo"); ui.setupUi(this); const QDate today = QDate::currentDate(); ui.expiryDE->setMinimumDate(today); ui.expiryDE->setDate(today.addYears(2)); ui.expiryCB->setChecked(true); ui.emailLW->setDefaultValue(i18n("new email")); ui.dnsLW->setDefaultValue(i18n("new dns name")); ui.uriLW->setDefaultValue(i18n("new uri")); fillKeySizeComboBoxen(); } void setProtocol(GpgME::Protocol proto) { if (protocol == proto) { return; } protocol = proto; loadDefaultKeyType(); } void setAdditionalUserIDs(const QStringList &items) { ui.uidLW->setItems(items); } QStringList additionalUserIDs() const { return ui.uidLW->items(); } void setAdditionalEMailAddresses(const QStringList &items) { ui.emailLW->setItems(items); } QStringList additionalEMailAddresses() const { return ui.emailLW->items(); } void setDnsNames(const QStringList &items) { ui.dnsLW->setItems(items); } QStringList dnsNames() const { return ui.dnsLW->items(); } void setUris(const QStringList &items) { ui.uriLW->setItems(items); } QStringList uris() const { return ui.uriLW->items(); } void setKeyStrength(unsigned int strength) { set_keysize(ui.rsaKeyStrengthCB, strength); set_keysize(ui.dsaKeyStrengthCB, strength); } unsigned int keyStrength() const { return ui.dsaRB->isChecked() ? get_keysize(ui.dsaKeyStrengthCB) : ui.rsaRB->isChecked() ? get_keysize(ui.rsaKeyStrengthCB) : 0; } void setKeyType(Subkey::PubkeyAlgo algo) { QRadioButton *const rb = is_rsa(algo) ? ui.rsaRB : is_dsa(algo) ? ui.dsaRB : is_ecdsa(algo) || is_eddsa(algo) ? ui.ecdsaRB : nullptr; if (rb) { rb->setChecked(true); } } Subkey::PubkeyAlgo keyType() const { return ui.dsaRB->isChecked() ? Subkey::AlgoDSA : ui.rsaRB->isChecked() ? Subkey::AlgoRSA : ui.ecdsaRB->isChecked() ? ui.ecdsaKeyCurvesCB->currentText() == QLatin1String("ed25519") ? Subkey::AlgoEDDSA : Subkey::AlgoECDSA : Subkey::AlgoUnknown; } void setKeyCurve(const QString &curve) { set_curve(ui.ecdsaKeyCurvesCB, curve); } QString keyCurve() const { return get_curve(ui.ecdsaKeyCurvesCB); } void setSubkeyType(Subkey::PubkeyAlgo algo) { ui.elgCB->setChecked(is_elg(algo)); ui.rsaSubCB->setChecked(is_rsa(algo)); ui.ecdhCB->setChecked(is_ecdh(algo)); } Subkey::PubkeyAlgo subkeyType() const { if (ui.elgCB->isChecked()) { return Subkey::AlgoELG_E; } else if (ui.rsaSubCB->isChecked()) { return Subkey::AlgoRSA; } else if (ui.ecdhCB->isChecked()) { return Subkey::AlgoECDH; } return Subkey::AlgoUnknown; } void setSubkeyCurve(const QString &curve) { set_curve(ui.ecdhKeyCurvesCB, curve); } QString subkeyCurve() const { return get_curve(ui.ecdhKeyCurvesCB); } void setSubkeyStrength(unsigned int strength) { if (subkeyType() == Subkey::AlgoRSA) { set_keysize(ui.rsaKeyStrengthSubCB, strength); } else { set_keysize(ui.elgKeyStrengthCB, strength); } } unsigned int subkeyStrength() const { if (subkeyType() == Subkey::AlgoRSA) { return get_keysize(ui.rsaKeyStrengthSubCB); } return get_keysize(ui.elgKeyStrengthCB); } void setSigningAllowed(bool on) { ui.signingCB->setChecked(on); } bool signingAllowed() const { return ui.signingCB->isChecked(); } void setEncryptionAllowed(bool on) { ui.encryptionCB->setChecked(on); } bool encryptionAllowed() const { return ui.encryptionCB->isChecked(); } void setCertificationAllowed(bool on) { ui.certificationCB->setChecked(on); } bool certificationAllowed() const { return ui.certificationCB->isChecked(); } void setAuthenticationAllowed(bool on) { ui.authenticationCB->setChecked(on); } bool authenticationAllowed() const { return ui.authenticationCB->isChecked(); } void setExpiryDate(QDate date) { if (date.isValid()) { ui.expiryDE->setDate(date); } else { ui.expiryCB->setChecked(false); } } QDate expiryDate() const { return ui.expiryCB->isChecked() ? ui.expiryDE->date() : QDate(); } Q_SIGNALS: void changed(); private Q_SLOTS: void slotKeyMaterialSelectionChanged() { const unsigned int algo = keyType(); const unsigned int sk_algo = subkeyType(); if (protocol == OpenPGP) { if (!keyTypeImmutable) { ui.elgCB->setEnabled(is_dsa(algo)); ui.rsaSubCB->setEnabled(is_rsa(algo)); ui.ecdhCB->setEnabled(is_ecdsa(algo) || is_eddsa(algo)); if (sender() == ui.dsaRB || sender() == ui.rsaRB || sender() == ui.ecdsaRB) { ui.elgCB->setChecked(is_dsa(algo)); ui.ecdhCB->setChecked(is_ecdsa(algo) || is_eddsa(algo)); ui.rsaSubCB->setChecked(is_rsa(algo)); } if (is_rsa(algo)) { ui.encryptionCB->setEnabled(true); ui.encryptionCB->setChecked(true); ui.signingCB->setEnabled(true); ui.signingCB->setChecked(true); ui.authenticationCB->setEnabled(true); if (is_rsa(sk_algo)) { ui.encryptionCB->setEnabled(false); ui.encryptionCB->setChecked(true); } else { ui.encryptionCB->setEnabled(true); } } else if (is_dsa(algo)) { ui.encryptionCB->setEnabled(false); if (is_elg(sk_algo)) { ui.encryptionCB->setChecked(true); } else { ui.encryptionCB->setChecked(false); } } else if (is_ecdsa(algo) || is_eddsa(algo)) { ui.signingCB->setEnabled(true); ui.signingCB->setChecked(true); ui.authenticationCB->setEnabled(true); ui.encryptionCB->setEnabled(false); ui.encryptionCB->setChecked(is_ecdh(sk_algo)); } } } else { //assert( is_rsa( keyType() ) ); // it can happen through misconfiguration by the admin that no key type is selectable at all } } void slotSigningAllowedToggled(bool on) { if (!on && protocol == CMS && !encryptionAllowed()) { setEncryptionAllowed(true); } } void slotEncryptionAllowedToggled(bool on) { if (!on && protocol == CMS && !signingAllowed()) { setSigningAllowed(true); } } private: void fillKeySizeComboBoxen(); void loadDefaultKeyType(); void loadDefaultGnuPGKeyType(); void updateWidgetVisibility(); private: GpgME::Protocol protocol; unsigned int pgpDefaultAlgorithm; unsigned int cmsDefaultAlgorithm; bool keyTypeImmutable; Ui_AdvancedSettingsDialog ui; bool mECCSupported; bool mEdDSASupported; }; class ChooseProtocolPage : public WizardPage { Q_OBJECT public: explicit ChooseProtocolPage(QWidget *p = nullptr) : WizardPage(p), initialized(false), ui() { ui.setupUi(this); registerField(QStringLiteral("pgp"), ui.pgpCLB); } void setProtocol(Protocol proto) { if (proto == OpenPGP) { ui.pgpCLB->setChecked(true); } else if (proto == CMS) { ui.x509CLB->setChecked(true); } else { force_set_checked(ui.pgpCLB, false); force_set_checked(ui.x509CLB, false); } } Protocol protocol() const { return ui.pgpCLB->isChecked() ? OpenPGP : ui.x509CLB->isChecked() ? CMS : UnknownProtocol; } void initializePage() override { if (!initialized) { connect(ui.pgpCLB, &QAbstractButton::clicked, wizard(), &QWizard::next, Qt::QueuedConnection); connect(ui.x509CLB, &QAbstractButton::clicked, wizard(), &QWizard::next, Qt::QueuedConnection); } initialized = true; } bool isComplete() const override { return protocol() != UnknownProtocol; } private: bool initialized : 1; Ui_ChooseProtocolPage ui; }; struct Line { QString attr; QString label; QString regex; QLineEdit *edit; }; class EnterDetailsPage : public WizardPage { Q_OBJECT public: explicit EnterDetailsPage(QWidget *p = nullptr) : WizardPage(p), dialog(this), ui() { ui.setupUi(this); // set errorLB to have a fixed height of two lines: ui.errorLB->setText(QStringLiteral("2<br>1")); ui.errorLB->setFixedHeight(ui.errorLB->minimumSizeHint().height()); ui.errorLB->clear(); connect(ui.resultLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); // The email doesn't necessarily show up in ui.resultLE: connect(ui.emailLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); registerDialogPropertiesAsFields(); registerField(QStringLiteral("dn"), ui.resultLE); registerField(QStringLiteral("name"), ui.nameLE); registerField(QStringLiteral("email"), ui.emailLE); registerField(QStringLiteral("protectedKey"), ui.withPassCB); updateForm(); setCommitPage(true); setButtonText(QWizard::CommitButton, i18nc("@action", "Create")); const auto conf = QGpgME::cryptoConfig(); if (!conf) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig."; return; } const auto entry = conf->entry(QStringLiteral("gpg-agent"), QStringLiteral("Passphrase policy"), QStringLiteral("enforce-passphrase-constraints")); if (entry && entry->boolValue()) { qCDebug(KLEOPATRA_LOG) << "Disabling passphrace cb because of agent config."; ui.withPassCB->setEnabled(false); ui.withPassCB->setChecked(true); } else { const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); ui.withPassCB->setChecked(config.readEntry("WithPassphrase", false)); ui.withPassCB->setEnabled(!config.isEntryImmutable("WithPassphrase")); } } bool isComplete() const override; void initializePage() override { updateForm(); dialog.setProtocol(pgp() ? OpenPGP : CMS); } void cleanupPage() override { saveValues(); } private: void updateForm(); void clearForm(); void saveValues(); void registerDialogPropertiesAsFields(); private: QString pgpUserID() const; QString cmsDN() const; private Q_SLOTS: void slotAdvancedSettingsClicked(); void slotUpdateResultLabel() { ui.resultLE->setText(pgp() ? pgpUserID() : cmsDN()); ui.withPassCB->setVisible(pgp()); } private: QVector<Line> lineList; QList<QWidget *> dynamicWidgets; QMap<QString, QString> savedValues; AdvancedSettingsDialog dialog; Ui_EnterDetailsPage ui; }; class KeyCreationPage : public WizardPage { Q_OBJECT public: explicit KeyCreationPage(QWidget *p = nullptr) : WizardPage(p), ui() { ui.setupUi(this); } bool isComplete() const override { return !job; } void initializePage() override { startJob(); } private: void startJob() { const auto proto = pgp() ? QGpgME::openpgp() : QGpgME::smime(); if (!proto) { return; } QGpgME::KeyGenerationJob *const j = proto->keyGenerationJob(); if (!j) { return; } if (!protectedKey() && pgp()) { auto ctx = QGpgME::Job::context(j); ctx->setPassphraseProvider(&mEmptyPWProvider); ctx->setPinentryMode(Context::PinentryLoopback); } connect(j, &QGpgME::KeyGenerationJob::result, this, &KeyCreationPage::slotResult); if (const Error err = j->start(createGnupgKeyParms())) setField(QStringLiteral("error"), i18n("Could not start key pair creation: %1", QString::fromLocal8Bit(err.asString()))); else { job = j; } } QStringList keyUsages() const; QStringList subkeyUsages() const; QString createGnupgKeyParms() const; EmptyPassphraseProvider mEmptyPWProvider; private Q_SLOTS: void slotResult(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog) { Q_UNUSED(auditLog) if (result.error().code() || (pgp() && !result.fingerprint())) { setField(QStringLiteral("error"), result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not create key pair: %1", QString::fromLocal8Bit(result.error().asString()))); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), QString()); } else if (pgp()) { setField(QStringLiteral("error"), QString()); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), i18n("Key pair created successfully.\n" "Fingerprint: %1", QLatin1String(result.fingerprint()))); } else { QFile file(tmpDir().absoluteFilePath(QStringLiteral("request.p10"))); if (!file.open(QIODevice::WriteOnly)) { setField(QStringLiteral("error"), i18n("Could not write output file %1: %2", file.fileName(), file.errorString())); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), QString()); } else { file.write(request); setField(QStringLiteral("error"), QString()); setField(QStringLiteral("url"), QUrl::fromLocalFile(file.fileName()).toString()); setField(QStringLiteral("result"), i18n("Key pair created successfully.")); } } // Ensure that we have the key in the keycache if (pgp() && !result.error().code() && result.fingerprint()) { auto ctx = Context::createForProtocol(OpenPGP); if (ctx) { // Check is pretty useless something very buggy in that case. Error e; const auto key = ctx->key(result.fingerprint(), e, true); if (!key.isNull()) { KeyCache::mutableInstance()->insert(key); } else { qCDebug(KLEOPATRA_LOG) << "Failed to find newly generated key."; } delete ctx; } } setField(QStringLiteral("fingerprint"), result.fingerprint() ? QString::fromLatin1(result.fingerprint()) : QString()); job = nullptr; Q_EMIT completeChanged(); const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); if (config.readEntry("SkipResultPage", false)) { if (result.fingerprint()) { KleopatraApplication::instance()->slotActivateRequested(QStringList() << QStringLiteral("kleopatra") << QStringLiteral("--query") << QLatin1String(result.fingerprint()), QString()); QMetaObject::invokeMethod(wizard(), "close", Qt::QueuedConnection); } else { QMetaObject::invokeMethod(wizard(), "next", Qt::QueuedConnection); } } else { QMetaObject::invokeMethod(wizard(), "next", Qt::QueuedConnection); } } private: QPointer<QGpgME::KeyGenerationJob> job; Ui_KeyCreationPage ui; }; class ResultPage : public WizardPage { Q_OBJECT public: explicit ResultPage(QWidget *p = nullptr) : WizardPage(p), initialized(false), successfullyCreatedSigningCertificate(false), successfullyCreatedEncryptionCertificate(false), ui() { ui.setupUi(this); ui.dragQueen->setPixmap(QIcon::fromTheme(QStringLiteral("kleopatra")).pixmap(64, 64)); registerField(QStringLiteral("error"), ui.errorTB, "plainText"); registerField(QStringLiteral("result"), ui.resultTB, "plainText"); registerField(QStringLiteral("url"), ui.dragQueen, "url"); // hidden field, since QWizard can't deal with non-widget-backed fields... QLineEdit *le = new QLineEdit(this); le->hide(); registerField(QStringLiteral("fingerprint"), le); } void initializePage() override { const bool error = isError(); if (error) { setTitle(i18nc("@title", "Key Creation Failed")); setSubTitle(i18n("Key pair creation failed. Please find details about the failure below.")); } else { setTitle(i18nc("@title", "Key Pair Successfully Created")); setSubTitle(i18n("Your new key pair was created successfully. Please find details on the result and some suggested next steps below.")); } ui.resultTB ->setVisible(!error); ui.errorTB ->setVisible(error); ui.dragQueen ->setVisible(!error &&!pgp()); ui.restartWizardPB ->setVisible(error); ui.nextStepsGB ->setVisible(!error); ui.saveRequestToFilePB ->setVisible(!pgp()); ui.makeBackupPB ->setVisible(pgp()); ui.createRevocationRequestPB->setVisible(pgp() &&false); // not implemented ui.sendCertificateByEMailPB ->setVisible(pgp()); ui.sendRequestByEMailPB ->setVisible(!pgp()); ui.uploadToKeyserverPB ->setVisible(pgp()); if (!error && !pgp()) { if (signingAllowed() && !encryptionAllowed()) { successfullyCreatedSigningCertificate = true; } else if (!signingAllowed() && encryptionAllowed()) { successfullyCreatedEncryptionCertificate = true; } else { successfullyCreatedEncryptionCertificate = successfullyCreatedSigningCertificate = true; } } ui.createSigningCertificatePB->setVisible(successfullyCreatedEncryptionCertificate &&!successfullyCreatedSigningCertificate); ui.createEncryptionCertificatePB->setVisible(successfullyCreatedSigningCertificate &&!successfullyCreatedEncryptionCertificate); setButtonVisible(QWizard::CancelButton, error); if (!initialized) connect(ui.restartWizardPB, &QAbstractButton::clicked, wizard(), &QWizard::restart); initialized = true; } void cleanupPage() override { setButtonVisible(QWizard::CancelButton, true); } bool isError() const { return !ui.errorTB->document()->isEmpty(); } bool isComplete() const override { return !isError(); } private: Key key() const { return KeyCache::instance()->findByFingerprint(fingerprint().toLatin1().constData()); } private Q_SLOTS: void slotSaveRequestToFile() { QString fileName = FileDialog::getSaveFileName(this, i18nc("@title", "Save Request"), QStringLiteral("imp"), i18n("PKCS#10 Requests (*.p10)")); if (fileName.isEmpty()) { return; } if (!fileName.endsWith(QLatin1String(".p10"), Qt::CaseInsensitive)) { fileName += QLatin1String(".p10"); } QFile src(QUrl(url()).toLocalFile()); if (!src.copy(fileName)) KMessageBox::error(this, xi18nc("@info", "Could not copy temporary file <filename>%1</filename> " "to file <filename>%2</filename>: <message>%3</message>", src.fileName(), fileName, src.errorString()), i18nc("@title", "Error Saving Request")); else KMessageBox::information(this, xi18nc("@info", "<para>Successfully wrote request to <filename>%1</filename>.</para>" "<para>You should now send the request to the Certification Authority (CA).</para>", fileName), i18nc("@title", "Request Saved")); } void slotSendRequestByEMail() { if (pgp()) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); invokeMailer(config.readEntry("CAEmailAddress"), // to i18n("Please process this certificate."), // subject i18n("Please process this certificate and inform the sender about the location to fetch the resulting certificate.\n\nThanks,\n"), // body QUrl(url()).toLocalFile()); // attachment } void slotSendCertificateByEMail() { if (!pgp() || exportCertificateCommand) { return; } ExportCertificateCommand *cmd = new ExportCertificateCommand(key()); connect(cmd, &ExportCertificateCommand::finished, this, &ResultPage::slotSendCertificateByEMailContinuation); cmd->setOpenPGPFileName(tmpDir().absoluteFilePath(fingerprint() + QLatin1String(".asc"))); cmd->start(); exportCertificateCommand = cmd; } void slotSendCertificateByEMailContinuation() { if (!exportCertificateCommand) { return; } // ### better error handling? const QString fileName = exportCertificateCommand->openPGPFileName(); qCDebug(KLEOPATRA_LOG) << "fileName" << fileName; exportCertificateCommand = nullptr; if (fileName.isEmpty()) { return; } invokeMailer(QString(), // to i18n("My new public OpenPGP key"), // subject i18n("Please find attached my new public OpenPGP key."), // body fileName); } QByteArray ol_quote(QByteArray str) { #ifdef Q_OS_WIN return "\"\"" + str.replace('"', "\\\"") + "\"\""; //return '"' + str.replace( '"', "\\\"" ) + '"'; #else return str; #endif } void invokeMailer(const QString &to, const QString &subject, const QString &body, const QString &attachment) { qCDebug(KLEOPATRA_LOG) << "to:" << to << "subject:" << subject << "body:" << body << "attachment:" << attachment; // RFC 2368 says body's linebreaks need to be encoded as // "%0D%0A", so normalize body to CRLF: //body.replace(QLatin1Char('\n'), QStringLiteral("\r\n")).remove(QStringLiteral("\r\r")); QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), subject); query.addQueryItem(QStringLiteral("body"), body); if (!attachment.isEmpty()) { query.addQueryItem(QStringLiteral("attach"), attachment); } QUrl url; url.setScheme(QStringLiteral("mailto")); url.setQuery(query); qCDebug(KLEOPATRA_LOG) << "openUrl" << url; QDesktopServices::openUrl(url); KMessageBox::information(this, xi18nc("@info", "<para><application>Kleopatra</application> tried to send a mail via your default mail client.</para>" "<para>Some mail clients are known not to support attachments when invoked this way.</para>" "<para>If your mail client does not have an attachment, then drag the <application>Kleopatra</application> icon and drop it on the message compose window of your mail client.</para>" "<para>If that does not work, either, save the request to a file, and then attach that.</para>"), i18nc("@title", "Sending Mail"), QStringLiteral("newcertificatewizard-mailto-troubles")); } void slotUploadCertificateToDirectoryServer() { if (pgp()) { (new ExportOpenPGPCertsToServerCommand(key()))->start(); } } void slotBackupCertificate() { if (pgp()) { (new ExportSecretKeyCommand(key()))->start(); } } void slotCreateRevocationRequest() { } void slotCreateSigningCertificate() { if (successfullyCreatedSigningCertificate) { return; } toggleSignEncryptAndRestart(); } void slotCreateEncryptionCertificate() { if (successfullyCreatedEncryptionCertificate) { return; } toggleSignEncryptAndRestart(); } private: void toggleSignEncryptAndRestart() { if (!wizard()) { return; } if (KMessageBox::warningContinueCancel( this, i18nc("@info", "This operation will delete the certification request. " "Please make sure that you have sent or saved it before proceeding."), i18nc("@title", "Certification Request About To Be Deleted")) != KMessageBox::Continue) { return; } const bool sign = signingAllowed(); const bool encr = encryptionAllowed(); setField(QStringLiteral("signingAllowed"), !sign); setField(QStringLiteral("encryptionAllowed"), !encr); // restart and skip to enter details Page: wizard()->restart(); for (int i = wizard()->currentId(); i < NewCertificateWizard::EnterDetailsPageId; ++i) { wizard()->next(); } } private: bool initialized : 1; bool successfullyCreatedSigningCertificate : 1; bool successfullyCreatedEncryptionCertificate : 1; QPointer<ExportCertificateCommand> exportCertificateCommand; Ui_ResultPage ui; }; } class NewCertificateWizard::Private { friend class ::Kleo::NewCertificateWizard; friend class ::Kleo::NewCertificateUi::WizardPage; NewCertificateWizard *const q; public: explicit Private(NewCertificateWizard *qq) : q(qq), tmp(QDir::temp().absoluteFilePath(QStringLiteral("kleo-"))), ui(q) { q->setWindowTitle(i18nc("@title:window", "Key Pair Creation Wizard")); } private: QTemporaryDir tmp; struct Ui { ChooseProtocolPage chooseProtocolPage; EnterDetailsPage enterDetailsPage; KeyCreationPage keyCreationPage; ResultPage resultPage; explicit Ui(NewCertificateWizard *q) : chooseProtocolPage(q), enterDetailsPage(q), keyCreationPage(q), resultPage(q) { KDAB_SET_OBJECT_NAME(chooseProtocolPage); KDAB_SET_OBJECT_NAME(enterDetailsPage); KDAB_SET_OBJECT_NAME(keyCreationPage); KDAB_SET_OBJECT_NAME(resultPage); q->setOptions(DisabledBackButtonOnLastPage); q->setPage(ChooseProtocolPageId, &chooseProtocolPage); q->setPage(EnterDetailsPageId, &enterDetailsPage); q->setPage(KeyCreationPageId, &keyCreationPage); q->setPage(ResultPageId, &resultPage); q->setStartId(ChooseProtocolPageId); } } ui; }; NewCertificateWizard::NewCertificateWizard(QWidget *p) : QWizard(p), d(new Private(this)) { } NewCertificateWizard::~NewCertificateWizard() {} void NewCertificateWizard::setProtocol(Protocol proto) { d->ui.chooseProtocolPage.setProtocol(proto); setStartId(proto == UnknownProtocol ? ChooseProtocolPageId : EnterDetailsPageId); } Protocol NewCertificateWizard::protocol() const { return d->ui.chooseProtocolPage.protocol(); } static QString pgpLabel(const QString &attr) { if (attr == QLatin1String("NAME")) { return i18n("Name"); } if (attr == QLatin1String("EMAIL")) { return i18n("EMail"); } return QString(); } static QString attributeLabel(const QString &attr, bool pgp) { if (attr.isEmpty()) { return QString(); } const QString label = pgp ? pgpLabel(attr) : Kleo::DNAttributeMapper::instance()->name2label(attr); if (!label.isEmpty()) if (pgp) { return label; } else return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr); else { return attr; } } #if 0 //Not used anywhere static QString attributeLabelWithColor(const QString &attr, bool pgp) { const QString result = attributeLabel(attr, pgp); if (result.isEmpty()) { return QString(); } else { return result + ':'; } } #endif static QString attributeFromKey(QString key) { return key.remove(QLatin1Char('!')); } QDir WizardPage::tmpDir() const { return wizard() ? QDir(wizard()->d->tmp.path()) : QDir::home(); } void EnterDetailsPage::registerDialogPropertiesAsFields() { const QMetaObject *const mo = dialog.metaObject(); for (unsigned int i = mo->propertyOffset(), end = i + mo->propertyCount(); i != end; ++i) { const QMetaProperty mp = mo->property(i); if (mp.isValid()) { registerField(QLatin1String(mp.name()), &dialog, mp.name(), SIGNAL(accepted())); } } } void EnterDetailsPage::saveValues() { for (const Line &line : qAsConst(lineList)) { savedValues[ attributeFromKey(line.attr) ] = line.edit->text().trimmed(); } } void EnterDetailsPage::clearForm() { qDeleteAll(dynamicWidgets); dynamicWidgets.clear(); lineList.clear(); ui.nameLE->hide(); ui.nameLE->clear(); ui.nameLB->hide(); ui.nameRequiredLB->hide(); ui.emailLE->hide(); ui.emailLE->clear(); ui.emailLB->hide(); ui.emailRequiredLB->hide(); } static int row_index_of(QWidget *w, QGridLayout *l) { const int idx = l->indexOf(w); int r, c, rs, cs; l->getItemPosition(idx, &r, &c, &rs, &cs); return r; } static QLineEdit *adjust_row(QGridLayout *l, int row, const QString &label, const QString &preset, QValidator *validator, bool readonly, bool required) { Q_ASSERT(l); Q_ASSERT(row >= 0); Q_ASSERT(row < l->rowCount()); QLabel *lb = qobject_cast<QLabel *>(l->itemAtPosition(row, 0)->widget()); Q_ASSERT(lb); QLineEdit *le = qobject_cast<QLineEdit *>(l->itemAtPosition(row, 1)->widget()); Q_ASSERT(le); lb->setBuddy(le); // For better accessibility QLabel *reqLB = qobject_cast<QLabel *>(l->itemAtPosition(row, 2)->widget()); Q_ASSERT(reqLB); lb->setText(i18nc("interpunctation for labels", "%1:", label)); le->setText(preset); reqLB->setText(required ? i18n("(required)") : i18n("(optional)")); delete le->validator(); if (validator) { if (!validator->parent()) { validator->setParent(le); } le->setValidator(validator); } le->setReadOnly(readonly && le->hasAcceptableInput()); lb->show(); le->show(); reqLB->show(); return le; } static int add_row(QGridLayout *l, QList<QWidget *> *wl) { Q_ASSERT(l); Q_ASSERT(wl); const int row = l->rowCount(); QWidget *w1, *w2, *w3; l->addWidget(w1 = new QLabel(l->parentWidget()), row, 0); l->addWidget(w2 = new QLineEdit(l->parentWidget()), row, 1); l->addWidget(w3 = new QLabel(l->parentWidget()), row, 2); wl->push_back(w1); wl->push_back(w2); wl->push_back(w3); return row; } void EnterDetailsPage::updateForm() { clearForm(); const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); QStringList attrOrder = config.readEntry(pgp() ? "OpenPGPAttributeOrder" : "DNAttributeOrder", QStringList()); if (attrOrder.empty()) { if (pgp()) { attrOrder << QStringLiteral("NAME") << QStringLiteral("EMAIL"); } else { attrOrder << QStringLiteral("CN!") << QStringLiteral("L") << QStringLiteral("OU") << QStringLiteral("O") << QStringLiteral("C") << QStringLiteral("EMAIL!"); } } QList<QWidget *> widgets; widgets.push_back(ui.nameLE); widgets.push_back(ui.emailLE); QMap<int, Line> lines; - Q_FOREACH (const QString &rawKey, attrOrder) { + for (const QString &rawKey : qAsConst(attrOrder)) { const QString key = rawKey.trimmed().toUpper(); const QString attr = attributeFromKey(key); if (attr.isEmpty()) { continue; } const QString preset = savedValues.value(attr, config.readEntry(attr, QString())); const bool required = key.endsWith(QLatin1Char('!')); const bool readonly = config.isEntryImmutable(attr); const QString label = config.readEntry(attr + QLatin1String("_label"), attributeLabel(attr, pgp())); const QString regex = config.readEntry(attr + QLatin1String("_regex")); int row; bool known = true; QValidator *validator = nullptr; if (attr == QLatin1String("EMAIL")) { row = row_index_of(ui.emailLE, ui.gridLayout); validator = regex.isEmpty() ? Validation::email() : Validation::email(QRegExp(regex)); } else if (attr == QLatin1String("NAME") || attr == QLatin1String("CN")) { if ((pgp() && attr == QLatin1String("CN")) || (!pgp() && attr == QLatin1String("NAME"))) { continue; } if (pgp()) { validator = regex.isEmpty() ? Validation::pgpName() : Validation::pgpName(QRegExp(regex)); } row = row_index_of(ui.nameLE, ui.gridLayout); } else { known = false; row = add_row(ui.gridLayout, &dynamicWidgets); } if (!validator && !regex.isEmpty()) { validator = new QRegExpValidator(QRegExp(regex), nullptr); } QLineEdit *le = adjust_row(ui.gridLayout, row, label, preset, validator, readonly, required); const Line line = { key, label, regex, le }; lines[row] = line; if (!known) { widgets.push_back(le); } // don't connect twice: disconnect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); connect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); } // create lineList in visual order, so requirementsAreMet() // complains from top to bottom: lineList.reserve(lines.count()); std::copy(lines.cbegin(), lines.cend(), std::back_inserter(lineList)); widgets.push_back(ui.resultLE); widgets.push_back(ui.advancedPB); if (ui.nameLE->text().isEmpty()) { ui.nameLE->setText(userFullName()); } if (ui.emailLE->text().isEmpty()) { ui.emailLE->setText(userEmailAddress()); } set_tab_order(widgets); } QString EnterDetailsPage::cmsDN() const { DN dn; for (QVector<Line>::const_iterator it = lineList.begin(), end = lineList.end(); it != end; ++it) { const QString text = it->edit->text().trimmed(); if (text.isEmpty()) { continue; } QString attr = attributeFromKey(it->attr); if (attr == QLatin1String("EMAIL")) { continue; } if (const char *const oid = oidForAttributeName(attr)) { attr = QString::fromUtf8(oid); } dn.append(DN::Attribute(attr, text)); } return dn.dn(); } QString EnterDetailsPage::pgpUserID() const { return Formatting::prettyNameAndEMail(OpenPGP, QString(), ui.nameLE->text().trimmed(), ui.emailLE->text().trimmed(), QString()); } static bool has_intermediate_input(const QLineEdit *le) { QString text = le->text(); int pos = le->cursorPosition(); const QValidator *const v = le->validator(); return v && v->validate(text, pos) == QValidator::Intermediate; } static bool requirementsAreMet(const QVector<Line> &list, QString &error) { bool allEmpty = true; for (const Line &line : list) { const QLineEdit *le = line.edit; if (!le) { continue; } const QString key = line.attr; qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\":"; if (le->text().trimmed().isEmpty()) { if (key.endsWith(QLatin1Char('!'))) { if (line.regex.isEmpty()) { error = xi18nc("@info", "<interface>%1</interface> is required, but empty.", line.label); } else error = xi18nc("@info", "<interface>%1</interface> is required, but empty.<nl/>" "Local Admin rule: <icode>%2</icode>", line.label, line.regex); return false; } } else if (has_intermediate_input(le)) { if (line.regex.isEmpty()) { error = xi18nc("@info", "<interface>%1</interface> is incomplete.", line.label); } else error = xi18nc("@info", "<interface>%1</interface> is incomplete.<nl/>" "Local Admin rule: <icode>%2</icode>", line.label, line.regex); return false; } else if (!le->hasAcceptableInput()) { if (line.regex.isEmpty()) { error = xi18nc("@info", "<interface>%1</interface> is invalid.", line.label); } else error = xi18nc("@info", "<interface>%1</interface> is invalid.<nl/>" "Local Admin rule: <icode>%2</icode>", line.label, line.regex); return false; } else { allEmpty = false; } } // Ensure that at least one value is acceptable return !allEmpty; } bool EnterDetailsPage::isComplete() const { QString error; const bool ok = requirementsAreMet(lineList, error); ui.errorLB->setText(error); return ok; } void EnterDetailsPage::slotAdvancedSettingsClicked() { dialog.exec(); } QStringList KeyCreationPage::keyUsages() const { QStringList usages; if (signingAllowed()) { usages << QStringLiteral("sign"); } if (encryptionAllowed() && !is_ecdh(subkeyType()) && !is_dsa(keyType()) && !is_rsa(subkeyType())) { usages << QStringLiteral("encrypt"); } if (authenticationAllowed()) { usages << QStringLiteral("auth"); } if (usages.empty() && certificationAllowed()) { /* Empty usages cause an error so we need to * add at least certify if nothing else is selected */ usages << QStringLiteral("cert"); } return usages; } QStringList KeyCreationPage::subkeyUsages() const { QStringList usages; if (encryptionAllowed() && (is_dsa(keyType()) || is_rsa(subkeyType()) || is_ecdh(subkeyType()))) { Q_ASSERT(subkeyType()); usages << QStringLiteral("encrypt"); } return usages; } namespace { template <typename T = QString> struct Row { QString key; T value; Row(const QString &k, const T &v) : key(k), value(v) {} }; template <typename T> QTextStream &operator<<(QTextStream &s, const Row<T> &row) { if (row.key.isEmpty()) { return s; } else { return s << "<tr><td>" << row.key << "</td><td>" << row.value << "</td></tr>"; } } } QString KeyCreationPage::createGnupgKeyParms() const { KeyParameters keyParameters(pgp() ? KeyParameters::OpenPGP : KeyParameters::CMS); keyParameters.setKeyType(keyType()); if (is_ecdsa(keyType()) || is_eddsa(keyType())) { keyParameters.setKeyCurve(keyCurve()); } else if (const unsigned int strength = keyStrength()) { keyParameters.setKeyLength(strength); } keyParameters.setKeyUsages(keyUsages()); if (subkeyType()) { keyParameters.setSubkeyType(subkeyType()); if (is_ecdh(subkeyType())) { keyParameters.setSubkeyCurve(subkeyCurve()); } else if (const unsigned int strength = subkeyStrength()) { keyParameters.setSubkeyLength(strength); } keyParameters.setSubkeyUsages(subkeyUsages()); } if (pgp()) { if (expiryDate().isValid()) { keyParameters.setExpirationDate(expiryDate()); } if (!name().isEmpty()) { keyParameters.setName(name()); } if (!email().isEmpty()) { keyParameters.setEmail(email()); } } else { keyParameters.setDN(dn()); keyParameters.setEmail(email()); Q_FOREACH (const QString &email, additionalEMailAddresses()) { keyParameters.addEmail(email); } Q_FOREACH (const QString &dns, dnsNames()) { keyParameters.addDomainName(dns); } Q_FOREACH (const QString &uri, uris()) { keyParameters.addURI(uri); } } const QString result = keyParameters.toString(); qCDebug(KLEOPATRA_LOG) << '\n' << result; return result; } static void fill_combobox(QComboBox &cb, const QList<int> &sizes, const QStringList &labels) { cb.clear(); for (int i = 0, end = sizes.size(); i != end; ++i) { const int size = std::abs(sizes[i]); /* As we respect the defaults configurable in GnuPG, and we also have configurable * defaults in Kleopatra its difficult to print out "default" here. To avoid confusion * about that its better not to show any default indication. */ cb.addItem(i < labels.size() && !labels[i].trimmed().isEmpty() ? i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit)", "%2 (%1 bits)", size, labels[i].trimmed()) : i18ncp("%1: key size in bits", "1 bit", "%1 bits", size), size); if (sizes[i] < 0) { cb.setCurrentIndex(cb.count() - 1); } } } void AdvancedSettingsDialog::fillKeySizeComboBoxen() { const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); QList<int> rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList<int>() << 2048 << -3072 << 4096); if (Kleo::gpgComplianceP("de-vs")) { rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList<int>() << -3072 << 4096); } const QList<int> dsaKeySizes = config.readEntry(DSA_KEYSIZES_ENTRY, QList<int>() << -2048); const QList<int> elgKeySizes = config.readEntry(ELG_KEYSIZES_ENTRY, QList<int>() << -2048 << 3072 << 4096); const QStringList rsaKeySizeLabels = config.readEntry(RSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList dsaKeySizeLabels = config.readEntry(DSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList elgKeySizeLabels = config.readEntry(ELG_KEYSIZE_LABELS_ENTRY, QStringList()); fill_combobox(*ui.rsaKeyStrengthCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui.rsaKeyStrengthSubCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui.dsaKeyStrengthCB, dsaKeySizes, dsaKeySizeLabels); fill_combobox(*ui.elgKeyStrengthCB, elgKeySizes, elgKeySizeLabels); if (mEdDSASupported) { // If supported we recommend cv25519 ui.ecdsaKeyCurvesCB->addItem(QStringLiteral("ed25519")); ui.ecdhKeyCurvesCB->addItem(QStringLiteral("cv25519")); } ui.ecdhKeyCurvesCB->addItems(curveNames); ui.ecdsaKeyCurvesCB->addItems(curveNames); } // Try to load the default key type from GnuPG void AdvancedSettingsDialog::loadDefaultGnuPGKeyType() { const auto conf = QGpgME::cryptoConfig(); if (!conf) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig."; return; } const auto entry = conf->entry(protocol == CMS ? QStringLiteral("gpgsm") : QStringLiteral("gpg"), QStringLiteral("Configuration"), QStringLiteral("default_pubkey_algo")); if (!entry) { qCDebug(KLEOPATRA_LOG) << "GnuPG does not have default key type. Fallback to RSA"; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); return; } qCDebug(KLEOPATRA_LOG) << "Have default key type: " << entry->stringValue(); // Format is <primarytype>[/usage]+<subkeytype>[/usage] const auto split = entry->stringValue().split(QLatin1Char('+')); int size = 0; Subkey::PubkeyAlgo algo = Subkey::AlgoUnknown; QString curve; parseAlgoString(split[0], &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setKeyType(algo); if (is_rsa(algo) || is_elg(algo) || is_dsa(algo)) { setKeyStrength(size); } else { setKeyCurve(curve); } if (split.size() == 2) { auto algoString = split[1]; // If it has no usage we assume encrypt subkey if (!algoString.contains(QLatin1Char('/'))) { algoString += QStringLiteral("/enc"); } parseAlgoString(algoString, &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setSubkeyType(algo); if (is_rsa(algo) || is_elg(algo)) { setSubkeyStrength(size); } else { setSubkeyCurve(curve); } } } void AdvancedSettingsDialog::loadDefaultKeyType() { if (protocol != CMS && protocol != OpenPGP) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); const QString entry = protocol == CMS ? QLatin1String(CMS_KEY_TYPE_ENTRY) : QLatin1String(PGP_KEY_TYPE_ENTRY); const QString keyType = config.readEntry(entry).trimmed().toUpper(); if (protocol == OpenPGP && keyType == QLatin1String("DSA")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoUnknown); } else if (protocol == OpenPGP && keyType == QLatin1String("DSA+ELG")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoELG_E); } else if (keyType.isEmpty() && engineIsVersion(2, 1, 17)) { loadDefaultGnuPGKeyType(); } else { if (!keyType.isEmpty() && keyType != QLatin1String("RSA")) qCWarning(KLEOPATRA_LOG) << "invalid value \"" << qPrintable(keyType) << "\" for entry \"[CertificateCreationWizard]" << qPrintable(entry) << "\""; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); } keyTypeImmutable = config.isEntryImmutable(entry); updateWidgetVisibility(); } void AdvancedSettingsDialog::updateWidgetVisibility() { // Personal Details Page if (protocol == OpenPGP) { // ### hide until multi-uid is implemented if (ui.tabWidget->indexOf(ui.personalTab) != -1) { ui.tabWidget->removeTab(ui.tabWidget->indexOf(ui.personalTab)); } } else { if (ui.tabWidget->indexOf(ui.personalTab) == -1) { ui.tabWidget->addTab(ui.personalTab, tr2i18n("Personal Details", nullptr)); } } ui.uidGB->setVisible(protocol == OpenPGP); ui.uidGB->setEnabled(false); ui.uidGB->setToolTip(i18nc("@info:tooltip", "Adding more than one User ID is not yet implemented.")); ui.emailGB->setVisible(protocol == CMS); ui.dnsGB->setVisible(protocol == CMS); ui.uriGB->setVisible(protocol == CMS); ui.ecdhCB->setVisible(mECCSupported); ui.ecdhKeyCurvesCB->setVisible(mECCSupported); ui.ecdsaKeyCurvesCB->setVisible(mECCSupported); ui.ecdsaRB->setVisible(mECCSupported); if (mEdDSASupported) { // We use the same radio button for EdDSA as we use for // ECDSA GnuPG does the same and this is really super technical // land. ui.ecdsaRB->setText(QStringLiteral("ECDSA/EdDSA")); } bool deVsHack = Kleo::gpgComplianceP("de-vs"); if (deVsHack) { // GnuPG Provides no API to query which keys are compliant for // a mode. If we request a different one it will error out so // we have to remove the options. // // Does anyone want to use NIST anyway? int i; while ((i = ui.ecdsaKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui.ecdsaKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui.ecdsaKeyCurvesCB->removeItem(i); } while ((i = ui.ecdhKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui.ecdhKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui.ecdhKeyCurvesCB->removeItem(i); } } // Technical Details Page if (keyTypeImmutable) { ui.rsaRB->setEnabled(false); ui.rsaSubCB->setEnabled(false); ui.dsaRB->setEnabled(false); ui.elgCB->setEnabled(false); ui.ecdsaRB->setEnabled(false); ui.ecdhCB->setEnabled(false); } else { ui.rsaRB->setEnabled(true); ui.rsaSubCB->setEnabled(protocol == OpenPGP); ui.dsaRB->setEnabled(protocol == OpenPGP && !deVsHack); ui.elgCB->setEnabled(protocol == OpenPGP && !deVsHack); ui.ecdsaRB->setEnabled(protocol == OpenPGP); ui.ecdhCB->setEnabled(protocol == OpenPGP); } ui.certificationCB->setVisible(protocol == OpenPGP); // gpgsm limitation? ui.authenticationCB->setVisible(protocol == OpenPGP); if (protocol == OpenPGP) { // pgp keys must have certify capability ui.certificationCB->setChecked(true); ui.certificationCB->setEnabled(false); } if (protocol == CMS) { ui.encryptionCB->setEnabled(true); ui.rsaSubCB->setChecked(false); ui.rsaKeyStrengthSubCB->setEnabled(false); } ui.expiryDE->setVisible(protocol == OpenPGP); ui.expiryCB->setVisible(protocol == OpenPGP); slotKeyMaterialSelectionChanged(); } #include "newcertificatewizard.moc" diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp index f1254f5e2..5180cb307 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,1121 +1,1121 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #include <config-kleopatra.h> #include <gpgme++/gpgmepp_version.h> #if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 # define QGPGME_HAS_DEBUG # define GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER #endif #include "readerstatus.h" #ifdef GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER # include "deviceinfowatcher.h" #endif #include "keypairinfo.h" #include <Libkleo/GnuPG> #include <Libkleo/FileSystemWatcher> #include <Libkleo/Stl_Util> #ifdef QGPGME_HAS_DEBUG # include <QGpgME/Debug> #endif #include <gpgme++/context.h> #include <gpgme++/defaultassuantransaction.h> #include <gpgme++/key.h> #include <gpg-error.h> #include "openpgpcard.h" #include "netkeycard.h" #include "pivcard.h" #include <QStringList> #include <QMutex> #include <QWaitCondition> #include <QThread> #include <QPointer> #include <QRegularExpression> #include <set> #include <list> #include <algorithm> #include <iterator> #include <utility> #include <cstdlib> #include "utils/kdtoolsglobal.h" #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; using namespace GpgME; static ReaderStatus *self = nullptr; #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) static const char *flags[] = { "NOCARD", "PRESENT", "ACTIVE", "USABLE", }; static_assert(sizeof flags / sizeof * flags == Card::_NumScdStates, ""); static const char *prettyFlags[] = { "NoCard", "CardPresent", "CardActive", "CardUsable", "CardError", }; static_assert(sizeof prettyFlags / sizeof * prettyFlags == Card::NumStates, ""); Q_DECLARE_METATYPE(GpgME::Error) namespace { static bool gpgHasMultiCardMultiAppSupport() { return !(engineInfo(GpgME::GpgEngine).engineVersion() < "2.3.0"); } static QDebug operator<<(QDebug s, const std::string &string) { return s << QString::fromStdString(string); } #ifndef QGPGME_HAS_DEBUG static QDebug operator<<(QDebug s, const GpgME::Error &err) { const bool oldSetting = s.autoInsertSpaces(); s.nospace() << err.asString() << " (code: " << err.code() << ", source: " << err.source() << ")"; s.setAutoInsertSpaces(oldSetting); return s.maybeSpace(); } #endif static QDebug operator<<(QDebug s, const std::vector< std::pair<std::string, std::string> > &v) { typedef std::pair<std::string, std::string> pair; s << '('; for (const pair &p : v) { s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n'; } return s << ')'; } struct CardApp { std::string serialNumber; std::string appName; }; static void logUnexpectedStatusLine(const std::pair<std::string, std::string> &line, const std::string &prefix = std::string(), const std::string &command = std::string()) { qCWarning(KLEOPATRA_LOG) << (!prefix.empty() ? QString::fromStdString(prefix + ": ") : QString()) << "Unexpected status line" << (!command.empty() ? QString::fromStdString(" on " + command + ":") : QLatin1String(":")) << QString::fromStdString(line.first) << QString::fromStdString(line.second); } static int parse_app_version(const std::string &s) { return std::atoi(s.c_str()); } static Card::PinState parse_pin_state(const QString &s) { bool ok; int i = s.toInt(&ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s; return Card::UnknownPinState; } switch (i) { case -4: return Card::NullPin; case -3: return Card::PinBlocked; case -2: return Card::NoPin; case -1: return Card::UnknownPinState; default: if (i < 0) { return Card::UnknownPinState; } else { return Card::PinOk; } } } template<typename T> static std::unique_ptr<T> gpgagent_transact(std::shared_ptr<Context> &gpgAgent, const char *command, std::unique_ptr<T> transaction, Error &err) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << ")"; err = gpgAgent->assuanTransact(command, std::move(transaction)); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << "): Error:" << err; if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) { qCDebug(KLEOPATRA_LOG) << "Assuan problem, killing context"; gpgAgent.reset(); } return std::unique_ptr<T>(); } std::unique_ptr<AssuanTransaction> t = gpgAgent->takeLastAssuanTransaction(); return std::unique_ptr<T>(dynamic_cast<T*>(t.release())); } static std::unique_ptr<DefaultAssuanTransaction> gpgagent_default_transact(std::shared_ptr<Context> &gpgAgent, const char *command, Error &err) { return gpgagent_transact(gpgAgent, command, std::unique_ptr<DefaultAssuanTransaction>(new DefaultAssuanTransaction), err); } static const std::string gpgagent_data(std::shared_ptr<Context> gpgAgent, const char *command, Error &err) { const std::unique_ptr<DefaultAssuanTransaction> t = gpgagent_default_transact(gpgAgent, command, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_data(" << command << "): got" << QString::fromStdString(t->data()); return t->data(); } else { qCDebug(KLEOPATRA_LOG) << "gpgagent_data(" << command << "): t == NULL"; return std::string(); } } static const std::vector< std::pair<std::string, std::string> > gpgagent_statuslines(std::shared_ptr<Context> gpgAgent, const char *what, Error &err) { const std::unique_ptr<DefaultAssuanTransaction> t = gpgagent_default_transact(gpgAgent, what, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): got" << t->statusLines(); return t->statusLines(); } else { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): t == NULL"; return std::vector<std::pair<std::string, std::string> >(); } } static const std::string gpgagent_status(const std::shared_ptr<Context> &gpgAgent, const char *what, Error &err) { const auto lines = gpgagent_statuslines (gpgAgent, what, 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 char *p = strrchr(what, ' '); const char *needle = (p + 1) ? (p + 1) : what; for (const auto &pair: lines) { if (pair.first == needle) { return pair.second; } } return std::string(); } static const std::string scd_getattr_status(std::shared_ptr<Context> &gpgAgent, const char *what, Error &err) { std::string cmd = "SCD GETATTR "; cmd += what; return gpgagent_status(gpgAgent, cmd.c_str(), err); } static const std::string getAttribute(std::shared_ptr<Context> &gpgAgent, const char *attribute, const char *versionHint) { Error err; const auto result = scd_getattr_status(gpgAgent, attribute, err); if (err) { if (err.code() == GPG_ERR_INV_NAME) { qCDebug(KLEOPATRA_LOG) << "Querying for attribute" << attribute << "not yet supported; needs GnuPG" << versionHint; } else { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR " << attribute << " failed:" << err; } return std::string(); } return result; } static std::vector<CardApp> getCardsAndApps(std::shared_ptr<Context> &gpgAgent, Error &err) { std::vector<CardApp> result; if (gpgHasMultiCardMultiAppSupport()) { const std::string command = "SCD GETINFO all_active_apps"; const auto statusLines = gpgagent_statuslines(gpgAgent, command.c_str(), err); if (err) { return result; } for (const auto &statusLine: statusLines) { if (statusLine.first == "SERIALNO") { const auto serialNumberAndApps = QByteArray::fromStdString(statusLine.second).split(' '); if (serialNumberAndApps.size() >= 2) { const auto serialNumber = serialNumberAndApps[0]; auto apps = serialNumberAndApps.mid(1); // sort the apps to get a stable order independently of the currently selected application std::sort(apps.begin(), apps.end()); for (const auto &app: apps) { qCDebug(KLEOPATRA_LOG) << "getCardsAndApps(): Found card" << serialNumber << "with app" << app; result.push_back({ serialNumber.toStdString(), app.toStdString() }); } } else { logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command); } } else { logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command); } } } else { // use SCD SERIALNO to get the currently active card const auto serialNumber = gpgagent_status(gpgAgent, "SCD SERIALNO", err); if (err) { return result; } // use SCD GETATTR APPTYPE to find out which app is active auto appName = scd_getattr_status(gpgAgent, "APPTYPE", err); std::transform(appName.begin(), appName.end(), appName.begin(), [](unsigned char c){ return std::tolower(c); }); if (err) { return result; } result.push_back({ serialNumber, appName }); } return result; } static std::string switchCard(std::shared_ptr<Context> &gpgAgent, const std::string &serialNumber, Error &err) { const std::string command = "SCD SWITCHCARD " + serialNumber; const auto statusLines = gpgagent_statuslines(gpgAgent, command.c_str(), err); if (err) { return std::string(); } if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second == serialNumber) { return serialNumber; } qCWarning(KLEOPATRA_LOG) << "switchCard():" << command << "returned" << statusLines << "(expected:" << "SERIALNO " + serialNumber << ")"; return std::string(); } static std::string switchApp(std::shared_ptr<Context> &gpgAgent, const std::string &serialNumber, const std::string &appName, Error &err) { const std::string command = "SCD SWITCHAPP " + appName; const auto statusLines = gpgagent_statuslines(gpgAgent, command.c_str(), err); if (err) { return std::string(); } if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second.find(serialNumber + ' ' + appName) == 0) { return appName; } qCWarning(KLEOPATRA_LOG) << "switchApp():" << command << "returned" << statusLines << "(expected:" << "SERIALNO " + serialNumber + ' ' + appName + "..." << ")"; return std::string(); } static const char * get_openpgp_card_manufacturer_from_serial_number(const std::string &serialno) { qCDebug(KLEOPATRA_LOG) << "get_openpgp_card_manufacturer_from_serial_number(" << serialno.c_str() << ")"; const bool isProperOpenPGPCardSerialNumber = serialno.size() == 32 && serialno.substr(0, 12) == "D27600012401"; if (isProperOpenPGPCardSerialNumber) { const char *sn = serialno.c_str(); const int manufacturerId = xtoi_2(sn + 16)*256 + xtoi_2(sn + 18); switch (manufacturerId) { case 0x0001: return "PPC Card Systems"; case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid"; case 0x0005: return "ZeitControl"; case 0x0006: return "Yubico"; case 0x0007: return "OpenKMS"; case 0x0008: return "LogoEmail"; case 0x002A: return "Magrathea"; case 0x1337: return "Warsaw Hackerspace"; case 0xF517: return "FSIJ"; /* 0x0000 and 0xFFFF are defined as test cards per spec, 0xFF00 to 0xFFFE are assigned for use with randomly created serial numbers. */ case 0x0000: case 0xffff: return "test card"; default: return (manufacturerId & 0xff00) == 0xff00 ? "unmanaged S/N range" : "unknown"; } } else { return "unknown"; } } static const std::string get_manufacturer(std::shared_ptr<Context> &gpgAgent, Error &err) { // The result of SCD GETATTR MANUFACTURER is the manufacturer ID as unsigned number // optionally followed by the name of the manufacturer, e.g. // 6 Yubico // 65534 unmanaged S/N range const auto manufacturerIdAndName = scd_getattr_status(gpgAgent, "MANUFACTURER", err); if (err.code()) { if (err.code() == GPG_ERR_INV_NAME) { qCDebug(KLEOPATRA_LOG) << "get_manufacturer(): Querying for attribute MANUFACTURER not yet supported; needs GnuPG 2.2.21+"; } else { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR MANUFACTURER failed:" << err; } return std::string(); } const auto startOfManufacturerName = manufacturerIdAndName.find(' '); if (startOfManufacturerName == std::string::npos) { // only ID without manufacturer name return "unknown"; } const auto manufacturerName = manufacturerIdAndName.substr(startOfManufacturerName + 1); return manufacturerName; } static bool isOpenPGPCardSerialNumber(const std::string &serialNumber) { return serialNumber.size() == 32 && serialNumber.substr(0, 12) == "D27600012401"; } static const std::string getDisplaySerialNumber(std::shared_ptr<Context> &gpgAgent, Error &err) { const auto displaySerialNumber = scd_getattr_status(gpgAgent, "$DISPSERIALNO", err); if (err && err.code() != GPG_ERR_INV_NAME) { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR $DISPSERIALNO failed:" << err; } return displaySerialNumber; } static void setDisplaySerialNumber(Card *card, std::shared_ptr<Context> &gpgAgent) { static const QRegularExpression leadingZeros(QStringLiteral("^0*")); Error err; const QString displaySerialNumber = QString::fromStdString(getDisplaySerialNumber(gpgAgent, err)); if (err) { card->setDisplaySerialNumber(QString::fromStdString(card->serialNumber())); return; } if (isOpenPGPCardSerialNumber(card->serialNumber()) && displaySerialNumber.size() == 12) { // add a space between manufacturer id and card id for OpenPGP cards card->setDisplaySerialNumber(displaySerialNumber.left(4) + QLatin1Char(' ') + displaySerialNumber.right(8)); } else { card->setDisplaySerialNumber(displaySerialNumber); } return; } static void handle_openpgp_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent) { Error err; auto pgpCard = new OpenPGPCard(*ci); pgpCard->setManufacturer(get_manufacturer(gpg_agent, err)); if (err.code()) { // fallback, e.g. if gpg does not yet support querying for the MANUFACTURER attribute pgpCard->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber())); } const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err.code()) { ci->setStatus(Card::CardError); return; } pgpCard->setCardInfo(info); setDisplaySerialNumber(pgpCard, gpg_agent); ci.reset(pgpCard); } static void readKeyPairInfoFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr<Context> &gpg_agent) { Error err; const std::string command = std::string("SCD READKEY --info-only -- ") + keyRef; const auto keyPairInfoLines = gpgagent_statuslines(gpg_agent, command.c_str(), err); if (err) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; return; } for (const auto &pair: keyPairInfoLines) { if (pair.first == "KEYPAIRINFO") { const KeyPairInfo info = KeyPairInfo::fromStatusLine(pair.second); if (info.grip.empty()) { qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" << QString::fromStdString(pair.second); continue; } pivCard->setKeyAlgorithm(keyRef, info.algorithm); } else { logUnexpectedStatusLine(pair, "readKeyPairInfoFromPIVCard()", command); } } } static void readCertificateFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr<Context> &gpg_agent) { Error err; const std::string command = std::string("SCD READCERT ") + keyRef; const std::string certificateData = gpgagent_data(gpg_agent, command.c_str(), err); if (err && err.code() != GPG_ERR_NOT_FOUND) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; return; } if (certificateData.empty()) { qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): No certificate stored on card"; return; } qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): Found certificate stored on card"; pivCard->setCertificateData(keyRef, certificateData); } static void handle_piv_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent) { Error err; auto pivCard = new PIVCard(*ci); const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } pivCard->setCardInfo(info); setDisplaySerialNumber(pivCard, gpg_agent); for (const KeyPairInfo &keyInfo : pivCard->keyInfos()) { if (!keyInfo.grip.empty()) { readKeyPairInfoFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent); readCertificateFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent); } } ci.reset(pivCard); } static void handle_netkey_card(std::shared_ptr<Card> &ci, std::shared_ptr<Context> &gpg_agent) { Error err; auto nkCard = new NetKeyCard(*ci); ci.reset(nkCard); ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err))); if (err.code()) { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR NKS-VERSION failed:" << err; ci->setErrorMsg(QStringLiteral ("NKS-VERSION failed: ") + QString::fromUtf8(err.asString())); return; } if (ci->appVersion() != 3) { qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 card, giving up. Version:" << ci->appVersion(); ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion())); return; } setDisplaySerialNumber(nkCard, gpg_agent); // the following only works for NKS v3... const auto chvStatus = QString::fromStdString( scd_getattr_status(gpg_agent, "CHV-STATUS", err)).split(QLatin1Char(' ')); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "Running SCD GETATTR CHV-STATUS failed:" << err; ci->setErrorMsg(QStringLiteral ("CHV-Status failed: ") + QString::fromUtf8(err.asString())); return; } std::vector<Card::PinState> states; states.reserve(chvStatus.count()); // CHV Status for NKS v3 is // Pin1 (Normal pin) Pin2 (Normal PUK) // SigG1 SigG PUK. int num = 0; for (const auto &state: chvStatus) { const auto parsed = parse_pin_state (state); states.push_back(parsed); if (parsed == Card::NullPin) { if (num == 0) { ci->setHasNullPin(true); } } ++num; } nkCard->setPinStates(states); const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } nkCard->setCardInfo(info); } static std::shared_ptr<Card> get_card_status(const std::string &serialNumber, const std::string &appName, std::shared_ptr<Context> &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << serialNumber << ',' << appName << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr<Card>(new Card()); if (gpgHasMultiCardMultiAppSupport()) { // select card Error err; const auto result = switchCard(gpg_agent, serialNumber, err); if (err) { if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); } else { ci->setStatus(Card::CardError); } return ci; } if (result.empty()) { qCWarning(KLEOPATRA_LOG) << "get_card_status: switching card failed"; ci->setStatus(Card::CardError); return ci; } ci->setStatus(Card::CardPresent); } else { ci->setStatus(Card::CardPresent); } if (gpgHasMultiCardMultiAppSupport()) { // select app Error err; const auto result = switchApp(gpg_agent, serialNumber, appName, err); if (err) { if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); } else { ci->setStatus(Card::CardError); } return ci; } if (result.empty()) { qCWarning(KLEOPATRA_LOG) << "get_card_status: switching app failed"; ci->setStatus(Card::CardError); return ci; } } ci->setSerialNumber(serialNumber); ci->setSigningKeyRef(getAttribute(gpg_agent, "$SIGNKEYID", "2.2.18")); ci->setEncryptionKeyRef(getAttribute(gpg_agent, "$ENCRKEYID", "2.2.18")); // Handle different card types if (appName == NetKeyCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end"; handle_netkey_card(ci, gpg_agent); return ci; } else if (appName == OpenPGPCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end"; handle_openpgp_card(ci, gpg_agent); return ci; } else if (appName == PIVCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end"; handle_piv_card(ci, gpg_agent); return ci; } else { qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << appName; return ci; } return ci; } static bool isCardNotPresentError(const GpgME::Error &err) { // see fixup_scd_errors() in gpg-card.c return err && ((err.code() == GPG_ERR_CARD_NOT_PRESENT) || ((err.code() == GPG_ERR_ENODEV || err.code() == GPG_ERR_CARD_REMOVED) && (err.sourceID() == GPG_ERR_SOURCE_SCD))); } static std::vector<std::shared_ptr<Card> > update_cardinfo(std::shared_ptr<Context> &gpgAgent) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo()"; // ensure that a card is present and that all cards are properly set up { Error err; const char *command = (gpgHasMultiCardMultiAppSupport()) ? "SCD SERIALNO --all" : "SCD SERIALNO"; const std::string serialno = gpgagent_status(gpgAgent, command, err); if (err) { if (isCardNotPresentError(err)) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present"; return std::vector<std::shared_ptr<Card> >(); } else { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; auto ci = std::shared_ptr<Card>(new Card()); ci->setStatus(Card::CardError); return std::vector<std::shared_ptr<Card> >(1, ci); } } } Error err; const std::vector<CardApp> cardApps = getCardsAndApps(gpgAgent, err); if (err) { if (isCardNotPresentError(err)) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present"; return std::vector<std::shared_ptr<Card> >(); } else { qCWarning(KLEOPATRA_LOG) << "Getting active apps on all inserted cards failed:" << err; auto ci = std::shared_ptr<Card>(new Card()); ci->setStatus(Card::CardError); return std::vector<std::shared_ptr<Card> >(1, ci); } } std::vector<std::shared_ptr<Card> > cards; for (const auto &cardApp: cardApps) { const auto card = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent); cards.push_back(card); } return cards; } } // namespace struct Transaction { CardApp cardApp; QByteArray command; QPointer<QObject> receiver; const char *slot; AssuanTransaction* assuanTransaction; }; static const Transaction updateTransaction = { { "__all__", "__all__" }, "__update__", nullptr, nullptr, nullptr }; static const Transaction quitTransaction = { { "__all__", "__all__" }, "__quit__", nullptr, nullptr, nullptr }; namespace { class ReaderStatusThread : public QThread { Q_OBJECT public: explicit ReaderStatusThread(QObject *parent = nullptr) : QThread(parent), m_gnupgHomePath(Kleo::gnupgHomeDirectory()), m_transactions(1, updateTransaction) // force initial scan { connect(this, &ReaderStatusThread::oneTransactionFinished, this, &ReaderStatusThread::slotOneTransactionFinished); } std::vector<std::shared_ptr<Card> > cardInfos() const { const QMutexLocker locker(&m_mutex); return m_cardInfos; } Card::Status cardStatus(unsigned int slot) const { const QMutexLocker locker(&m_mutex); if (slot < m_cardInfos.size()) { return m_cardInfos[slot]->status(); } else { return Card::NoCard; } } void addTransaction(const Transaction &t) { const QMutexLocker locker(&m_mutex); m_transactions.push_back(t); m_waitForTransactions.wakeOne(); } Q_SIGNALS: void firstCardWithNullPinChanged(const std::string &serialNumber); void anyCardCanLearnKeysChanged(bool); void cardAdded(const std::string &serialNumber, const std::string &appName); void cardChanged(const std::string &serialNumber, const std::string &appName); void cardRemoved(const std::string &serialNumber, const std::string &appName); void oneTransactionFinished(const GpgME::Error &err); public Q_SLOTS: void deviceStatusChanged(const QByteArray &details) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::deviceStatusChanged(" << details << ")"; addTransaction(updateTransaction); } void ping() { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()"; addTransaction(updateTransaction); } void stop() { const QMutexLocker locker(&m_mutex); m_transactions.push_front(quitTransaction); m_waitForTransactions.wakeOne(); } private Q_SLOTS: void slotOneTransactionFinished(const GpgME::Error &err) { std::list<Transaction> ft; KDAB_SYNCHRONIZED(m_mutex) ft.splice(ft.begin(), m_finishedTransactions); - Q_FOREACH (const Transaction &t, ft) + for (const Transaction &t : qAsConst(ft)) if (t.receiver && t.slot && *t.slot) { QMetaObject::invokeMethod(t.receiver, t.slot, Qt::DirectConnection, Q_ARG(GpgME::Error, err)); } } private: void run() override { while (true) { std::shared_ptr<Context> gpgAgent; CardApp cardApp; QByteArray command; bool nullSlot = false; AssuanTransaction* assuanTransaction = nullptr; std::list<Transaction> item; std::vector<std::shared_ptr<Card> > oldCards; Error err; std::unique_ptr<Context> c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr<Context>(c.release()); KDAB_SYNCHRONIZED(m_mutex) { while (m_transactions.empty()) { // go to sleep waiting for more work: qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands"; m_waitForTransactions.wait(&m_mutex); } // splice off the first transaction without // copying, so we own it without really importing // it into this thread (the QPointer isn't // thread-safe): item.splice(item.end(), m_transactions, m_transactions.begin()); // make local copies of the interesting stuff so // we can release the mutex again: cardApp = item.front().cardApp; command = item.front().command; nullSlot = !item.front().slot; // we take ownership of the assuan transaction std::swap(assuanTransaction, item.front().assuanTransaction); oldCards = m_cardInfos; } qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot; // now, let's see what we got: if (nullSlot && command == quitTransaction.command) { return; // quit } if ((nullSlot && command == updateTransaction.command)) { std::vector<std::shared_ptr<Card> > newCards = update_cardinfo(gpgAgent); KDAB_SYNCHRONIZED(m_mutex) m_cardInfos = newCards; bool anyLC = false; std::string firstCardWithNullPin; bool anyError = false; for (const auto &newCard: newCards) { const auto serialNumber = newCard->serialNumber(); const auto appName = newCard->appName(); const auto matchingOldCard = std::find_if(oldCards.cbegin(), oldCards.cend(), [serialNumber, appName] (const std::shared_ptr<Card> &card) { return card->serialNumber() == serialNumber && card->appName() == appName; }); if (matchingOldCard == oldCards.cend()) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added"; Q_EMIT cardAdded(serialNumber, appName); } else { if (*newCard != **matchingOldCard) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed"; Q_EMIT cardChanged(serialNumber, appName); } oldCards.erase(matchingOldCard); } if (newCard->canLearnKeys()) { anyLC = true; } if (newCard->hasNullPin() && firstCardWithNullPin.empty()) { firstCardWithNullPin = newCard->serialNumber(); } if (newCard->status() == Card::CardError) { anyError = true; } } for (const auto &oldCard: oldCards) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << oldCard->serialNumber() << "with app" << oldCard->appName() << "was removed"; Q_EMIT cardRemoved(oldCard->serialNumber(), oldCard->appName()); } Q_EMIT firstCardWithNullPinChanged(firstCardWithNullPin); Q_EMIT anyCardCanLearnKeysChanged(anyLC); if (anyError) { gpgAgent.reset(); } } else { GpgME::Error err; if (gpgHasMultiCardMultiAppSupport()) { switchCard(gpgAgent, cardApp.serialNumber, err); if (!err) { switchApp(gpgAgent, cardApp.serialNumber, cardApp.appName, err); } } if (!err) { if (assuanTransaction) { (void)gpgagent_transact(gpgAgent, command.constData(), std::unique_ptr<AssuanTransaction>(assuanTransaction), err); } else { (void)gpgagent_default_transact(gpgAgent, command.constData(), err); } } KDAB_SYNCHRONIZED(m_mutex) // splice 'item' into m_finishedTransactions: m_finishedTransactions.splice(m_finishedTransactions.end(), item); Q_EMIT oneTransactionFinished(err); } } } private: mutable QMutex m_mutex; QWaitCondition m_waitForTransactions; const QString m_gnupgHomePath; // protected by m_mutex: std::vector<std::shared_ptr<Card> > m_cardInfos; std::list<Transaction> m_transactions, m_finishedTransactions; }; } class ReaderStatus::Private : ReaderStatusThread { friend class Kleo::SmartCard::ReaderStatus; ReaderStatus *const q; public: explicit Private(ReaderStatus *qq) : ReaderStatusThread(qq), q(qq), watcher() { KDAB_SET_OBJECT_NAME(watcher); qRegisterMetaType<Card::Status>("Kleo::SmartCard::Card::Status"); qRegisterMetaType<GpgME::Error>("GpgME::Error"); connect(this, &::ReaderStatusThread::cardAdded, q, &ReaderStatus::cardAdded); connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged); connect(this, &::ReaderStatusThread::cardRemoved, q, &ReaderStatus::cardRemoved); connect(this, &::ReaderStatusThread::firstCardWithNullPinChanged, q, &ReaderStatus::firstCardWithNullPinChanged); connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged, q, &ReaderStatus::anyCardCanLearnKeysChanged); #ifdef GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER if (DeviceInfoWatcher::isSupported()) { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using new DeviceInfoWatcher"; connect(&devInfoWatcher, &DeviceInfoWatcher::statusChanged, this, &::ReaderStatusThread::deviceStatusChanged); } else #endif { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using deprecated FileSystemWatcher"; watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status"))); watcher.addPath(Kleo::gnupgHomeDirectory()); watcher.setDelay(100); connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping); } } ~Private() { stop(); if (!wait(100)) { terminate(); wait(); } } private: std::string firstCardWithNullPinImpl() const { const auto cis = cardInfos(); const auto firstWithNullPin = std::find_if(cis.cbegin(), cis.cend(), [](const std::shared_ptr<Card> &ci) { return ci->hasNullPin(); }); return firstWithNullPin != cis.cend() ? (*firstWithNullPin)->serialNumber() : std::string(); } bool anyCardCanLearnKeysImpl() const { const auto cis = cardInfos(); return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr<Card> &ci) { return ci->canLearnKeys(); }); } private: FileSystemWatcher watcher; #ifdef GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER DeviceInfoWatcher devInfoWatcher; #endif }; ReaderStatus::ReaderStatus(QObject *parent) : QObject(parent), d(new Private(this)) { self = this; qRegisterMetaType<std::string>("std::string"); } ReaderStatus::~ReaderStatus() { self = nullptr; } // slot void ReaderStatus::startMonitoring() { d->start(); #ifdef GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER if (DeviceInfoWatcher::isSupported()) { d->devInfoWatcher.start(); } #endif } // static ReaderStatus *ReaderStatus::mutableInstance() { return self; } // static const ReaderStatus *ReaderStatus::instance() { return self; } Card::Status ReaderStatus::cardStatus(unsigned int slot) const { return d->cardStatus(slot); } std::string ReaderStatus::firstCardWithNullPin() const { return d->firstCardWithNullPinImpl(); } bool ReaderStatus::anyCardCanLearnKeys() const { return d->anyCardCanLearnKeysImpl(); } void ReaderStatus::startSimpleTransaction(const std::shared_ptr<Card> &card, const QByteArray &command, QObject *receiver, const char *slot) { const CardApp cardApp = { card->serialNumber(), card->appName() }; const Transaction t = { cardApp, command, receiver, slot, nullptr }; d->addTransaction(t); } void ReaderStatus::startTransaction(const std::shared_ptr<Card> &card, const QByteArray &command, QObject *receiver, const char *slot, std::unique_ptr<AssuanTransaction> transaction) { const CardApp cardApp = { card->serialNumber(), card->appName() }; const Transaction t = { cardApp, command, receiver, slot, transaction.release() }; d->addTransaction(t); } void ReaderStatus::updateStatus() { d->ping(); } std::vector <std::shared_ptr<Card> > ReaderStatus::getCards() const { return d->cardInfos(); } std::shared_ptr<Card> ReaderStatus::getCard(const std::string &serialNumber, const std::string &appName) const { for (const auto &card: d->cardInfos()) { if (card->serialNumber() == serialNumber && card->appName() == appName) { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Found card with serial number" << serialNumber << "and app" << appName; return card; } } qCWarning(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Did not find card with serial number" << serialNumber << "and app" << appName; return std::shared_ptr<Card>(); } // static std::string ReaderStatus::switchCard(std::shared_ptr<Context>& ctx, const std::string& serialNumber, Error& err) { return ::switchCard(ctx, serialNumber, err); } // static std::string ReaderStatus::switchApp(std::shared_ptr<Context>& ctx, const std::string& serialNumber, const std::string& appName, Error& err) { return ::switchApp(ctx, serialNumber, appName, err); } // static Error ReaderStatus::switchCardAndApp(const std::string &serialNumber, const std::string &appName) { Error err; if (!(engineInfo(GpgEngine).engineVersion() < "2.3.0")) { std::unique_ptr<Context> c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return err; } auto assuanContext = std::shared_ptr<Context>(c.release()); const auto resultSerialNumber = switchCard(assuanContext, serialNumber, err); if (err || resultSerialNumber != serialNumber) { qCWarning(KLEOPATRA_LOG) << "Switching to card" << QString::fromStdString(serialNumber) << "failed"; if (!err) { err = Error::fromCode(GPG_ERR_UNEXPECTED); } return err; } const auto resultAppName = switchApp(assuanContext, serialNumber, appName, err); if (err || resultAppName != appName) { qCWarning(KLEOPATRA_LOG) << "Switching card to" << QString::fromStdString(appName) << "app failed"; if (!err) { err = Error::fromCode(GPG_ERR_UNEXPECTED); } return err; } } return err; } #include "readerstatus.moc" diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp index 2f64ead0c..abc2c1643 100644 --- a/src/view/keytreeview.cpp +++ b/src/view/keytreeview.cpp @@ -1,697 +1,697 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keytreeview.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 <config-kleopatra.h> #include "keytreeview.h" #include <Libkleo/KeyListModel> #include <Libkleo/KeyListSortFilterProxyModel> #include <Libkleo/KeyRearrangeColumnsProxyModel> #include <Libkleo/Predicates> #include "utils/headerview.h" #include "utils/tags.h" #include <Libkleo/Stl_Util> #include <Libkleo/KeyFilter> #include <Libkleo/KeyCache> #include <gpgme++/key.h> #include "kleopatra_debug.h" #include <QTimer> #include <QTreeView> #include <QHeaderView> #include <QItemSelectionModel> #include <QItemSelection> #include <QLayout> #include <QList> #include <QMenu> #include <QAction> #include <QEvent> #include <QContextMenuEvent> #include <KSharedConfig> #include <KLocalizedString> #include <gpgme++/gpgmepp_version.h> #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_REMARKS #endif #define TAGS_COLUMN 13 using namespace Kleo; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) namespace { class TreeView : public QTreeView { public: explicit TreeView(QWidget *parent = nullptr) : QTreeView(parent) { header()->installEventFilter(this); } QSize minimumSizeHint() const override { const QSize min = QTreeView::minimumSizeHint(); return QSize(min.width(), min.height() + 5 * fontMetrics().height()); } protected: bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched) if (event->type() == QEvent::ContextMenu) { QContextMenuEvent *e = static_cast<QContextMenuEvent *>(event); if (!mHeaderPopup) { mHeaderPopup = new QMenu(this); mHeaderPopup->setTitle(i18n("View Columns")); for (int i = 0; i < model()->columnCount(); ++i) { QAction *tmp = mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString()); tmp->setData(QVariant(i)); tmp->setCheckable(true); mColumnActions << tmp; } connect(mHeaderPopup, &QMenu::triggered, this, [this] (QAction *action) { const int col = action->data().toInt(); if ((col == TAGS_COLUMN) && action->isChecked()) { Tags::enableTags(); } if (action->isChecked()) { showColumn(col); } else { hideColumn(col); } KeyTreeView *tv = qobject_cast<KeyTreeView *> (parent()); if (tv) { tv->resizeColumns(); } }); } - foreach (QAction *action, mColumnActions) { - int column = action->data().toInt(); + for (QAction *action : qAsConst(mColumnActions)) { + const int column = action->data().toInt(); action->setChecked(!isColumnHidden(column)); } mHeaderPopup->popup(mapToGlobal(e->pos())); return true; } return false; } private: QMenu *mHeaderPopup = nullptr; QList<QAction *> mColumnActions; }; } // anon namespace KeyTreeView::KeyTreeView(QWidget *parent) : QWidget(parent), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(nullptr), m_view(new TreeView(this)), m_flatModel(nullptr), m_hierarchicalModel(nullptr), m_stringFilter(), m_keyFilter(), m_isHierarchical(true) { init(); } KeyTreeView::KeyTreeView(const KeyTreeView &other) : QWidget(nullptr), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr), m_view(new TreeView(this)), m_flatModel(other.m_flatModel), m_hierarchicalModel(other.m_hierarchicalModel), m_stringFilter(other.m_stringFilter), m_keyFilter(other.m_keyFilter), m_group(other.m_group), m_isHierarchical(other.m_isHierarchical) { init(); setColumnSizes(other.columnSizes()); setSortColumn(other.sortColumn(), other.sortOrder()); } KeyTreeView::KeyTreeView(const QString &text, const std::shared_ptr<KeyFilter> &kf, AbstractKeyListSortFilterProxyModel *proxy, QWidget *parent, const KConfigGroup &group) : QWidget(parent), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(proxy), m_view(new TreeView(this)), m_flatModel(nullptr), m_hierarchicalModel(nullptr), m_stringFilter(text), m_keyFilter(kf), m_group(group), m_isHierarchical(true), m_onceResized(false) { init(); } void KeyTreeView::setColumnSizes(const std::vector<int> &sizes) { if (sizes.empty()) { return; } Q_ASSERT(m_view); Q_ASSERT(m_view->header()); Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header())); if (HeaderView *const hv = static_cast<HeaderView *>(m_view->header())) { hv->setSectionSizes(sizes); } } void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder) { Q_ASSERT(m_view); m_view->sortByColumn(sortColumn, sortOrder); } int KeyTreeView::sortColumn() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); return m_view->header()->sortIndicatorSection(); } Qt::SortOrder KeyTreeView::sortOrder() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); return m_view->header()->sortIndicatorOrder(); } std::vector<int> KeyTreeView::columnSizes() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header())); if (HeaderView *const hv = static_cast<HeaderView *>(m_view->header())) { return hv->sectionSizes(); } else { return std::vector<int>(); } } void KeyTreeView::init() { KDAB_SET_OBJECT_NAME(m_proxy); KDAB_SET_OBJECT_NAME(m_view); if (!m_group.isValid()) { m_group = KSharedConfig::openConfig()->group("KeyTreeView_default"); } else { // Reopen as non const KConfig *conf = m_group.config(); m_group = conf->group(m_group.name()); } if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) { KDAB_SET_OBJECT_NAME(m_additionalProxy); } QLayout *layout = new QVBoxLayout(this); KDAB_SET_OBJECT_NAME(layout); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_view); HeaderView *headerView = new HeaderView(Qt::Horizontal); KDAB_SET_OBJECT_NAME(headerView); headerView->installEventFilter(m_view); headerView->setSectionsMovable(true); m_view->setHeader(headerView); m_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); //m_view->setAlternatingRowColors( true ); m_view->setAllColumnsShowFocus(true); m_view->setSortingEnabled(true); if (model()) { if (m_additionalProxy) { m_additionalProxy->setSourceModel(model()); } else { m_proxy->setSourceModel(model()); } } if (m_additionalProxy) { m_proxy->setSourceModel(m_additionalProxy); if (!m_additionalProxy->parent()) { m_additionalProxy->setParent(this); } } m_proxy->setFilterFixedString(m_stringFilter); m_proxy->setKeyFilter(m_keyFilter); m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); KeyRearrangeColumnsProxyModel *rearangingModel = new KeyRearrangeColumnsProxyModel(this); rearangingModel->setSourceModel(m_proxy); rearangingModel->setSourceColumns(QVector<int>() << KeyListModelInterface::PrettyName << KeyListModelInterface::PrettyEMail << KeyListModelInterface::Validity << KeyListModelInterface::ValidFrom << KeyListModelInterface::ValidUntil << KeyListModelInterface::TechnicalDetails << KeyListModelInterface::KeyID << KeyListModelInterface::Fingerprint << KeyListModelInterface::OwnerTrust << KeyListModelInterface::Origin << KeyListModelInterface::LastUpdate << KeyListModelInterface::Issuer << KeyListModelInterface::SerialNumber #ifdef GPGME_HAS_REMARKS // If a column is added before this TAGS_COLUMN define has to be updated accordingly << KeyListModelInterface::Remarks #endif ); m_view->setModel(rearangingModel); /* Handle expansion state */ m_expandedKeys = m_group.readEntry("Expanded", QStringList()); connect(m_view, &QTreeView::expanded, this, [this] (const QModelIndex &index) { if (!index.isValid()) { return; } const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value<GpgME::Key>(); const auto fpr = QString::fromLatin1(key.primaryFingerprint()); if (m_expandedKeys.contains(fpr)) { return; } m_expandedKeys << fpr; m_group.writeEntry("Expanded", m_expandedKeys); }); connect(m_view, &QTreeView::collapsed, this, [this] (const QModelIndex &index) { if (!index.isValid()) { return; } const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value<GpgME::Key>(); m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint())); m_group.writeEntry("Expanded", m_expandedKeys); }); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] () { /* We use a single shot timer here to ensure that the keysMayHaveChanged * handlers are all handled before we restore the expand state so that * the model is already populated. */ QTimer::singleShot(0, [this] () { restoreExpandState(); setUpTagKeys(); if (!m_onceResized) { m_onceResized = true; resizeColumns(); } }); }); resizeColumns(); restoreLayout(); } void KeyTreeView::restoreExpandState() { if (!KeyCache::instance()->initialized()) { qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting."; return; } for (const auto &fpr: qAsConst(m_expandedKeys)) { const KeyListModelInterface *km = dynamic_cast<const KeyListModelInterface*> (m_view->model()); if (!km) { qCWarning(KLEOPATRA_LOG) << "invalid model"; return; } const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData()); if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache"; m_expandedKeys.removeAll(fpr); return; } const auto idx = km->index(key); if (!idx.isValid()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model"; m_expandedKeys.removeAll(fpr); return; } m_view->expand(idx); } } void KeyTreeView::setUpTagKeys() { #ifdef GPGME_HAS_REMARKS const auto tagKeys = Tags::tagKeys(); if (m_hierarchicalModel) { m_hierarchicalModel->setRemarkKeys(tagKeys); } if (m_flatModel) { m_flatModel->setRemarkKeys(tagKeys); } #endif } void KeyTreeView::saveLayout() { QHeaderView *header = m_view->header(); QVariantList columnVisibility; QVariantList columnOrder; QVariantList columnWidths; const int headerCount = header->count(); columnVisibility.reserve(headerCount); columnWidths.reserve(headerCount); columnOrder.reserve(headerCount); for (int i = 0; i < headerCount; ++i) { columnVisibility << QVariant(!m_view->isColumnHidden(i)); columnWidths << QVariant(header->sectionSize(i)); columnOrder << QVariant(header->visualIndex(i)); } m_group.writeEntry("ColumnVisibility", columnVisibility); m_group.writeEntry("ColumnOrder", columnOrder); m_group.writeEntry("ColumnWidths", columnWidths); m_group.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); if (header->isSortIndicatorShown()) { m_group.writeEntry("SortColumn", header->sortIndicatorSection()); } else { m_group.writeEntry("SortColumn", -1); } } void KeyTreeView::restoreLayout() { QHeaderView *header = m_view->header(); QVariantList columnVisibility = m_group.readEntry("ColumnVisibility", QVariantList()); QVariantList columnOrder = m_group.readEntry("ColumnOrder", QVariantList()); QVariantList columnWidths = m_group.readEntry("ColumnWidths", QVariantList()); if (columnVisibility.isEmpty()) { // if config is empty then use default settings // The numbers have to be in line with the order in // setsSourceColumns above m_view->hideColumn(5); for (int i = 7; i < m_view->model()->columnCount(); ++i) { m_view->hideColumn(i); } if (KeyCache::instance()->initialized()) { QTimer::singleShot(0, this, &KeyTreeView::resizeColumns); } } else { for (int i = 0; i < header->count(); ++i) { if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) { // An additional column that was not around last time we saved. // We default to hidden. m_view->hideColumn(i); continue; } bool visible = columnVisibility[i].toBool(); int width = columnWidths[i].toInt(); int order = columnOrder[i].toInt(); header->resizeSection(i, width ? width : 100); header->moveSection(header->visualIndex(i), order); if ((i == TAGS_COLUMN) && visible) { Tags::enableTags(); } if (!visible) { m_view->hideColumn(i); } } m_onceResized = true; } int sortOrder = m_group.readEntry("SortAscending", (int)Qt::AscendingOrder); int sortColumn = m_group.readEntry("SortColumn", -1); if (sortColumn >= 0) { m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); } } KeyTreeView::~KeyTreeView() { saveLayout(); } static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm) { Q_ASSERT(pm); while (QAbstractProxyModel *const sm = qobject_cast<QAbstractProxyModel *>(pm->sourceModel())) { pm = sm; } return pm; } void KeyTreeView::setFlatModel(AbstractKeyListModel *model) { if (model == m_flatModel) { return; } m_flatModel = model; if (!m_isHierarchical) // TODO: this fails when called after setHierarchicalView( false )... { find_last_proxy(m_proxy)->setSourceModel(model); } } void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model) { if (model == m_hierarchicalModel) { return; } m_hierarchicalModel = model; if (m_isHierarchical) { find_last_proxy(m_proxy)->setSourceModel(model); m_view->expandAll(); for (int column = 0; column < m_view->header()->count(); ++column) { m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column))); } } } void KeyTreeView::setStringFilter(const QString &filter) { if (filter == m_stringFilter) { return; } m_stringFilter = filter; m_proxy->setFilterFixedString(filter); Q_EMIT stringFilterChanged(filter); } void KeyTreeView::setKeyFilter(const std::shared_ptr<KeyFilter> &filter) { if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) { return; } m_keyFilter = filter; m_proxy->setKeyFilter(filter); Q_EMIT keyFilterChanged(filter); } static QItemSelection itemSelectionFromKeys(const std::vector<Key> &keys, const KeyListSortFilterProxyModel &proxy) { QItemSelection result; for (const Key &key : keys) { const QModelIndex mi = proxy.index(key); if (mi.isValid()) { result.merge(QItemSelection(mi, mi), QItemSelectionModel::Select); } } return result; } void KeyTreeView::selectKeys(const std::vector<Key> &keys) { m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_proxy), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } std::vector<Key> KeyTreeView::selectedKeys() const { return m_proxy->keys(m_view->selectionModel()->selectedRows()); } void KeyTreeView::setHierarchicalView(bool on) { if (on == m_isHierarchical) { return; } if (on && !hierarchicalModel()) { qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set"; return; } if (!on && !flatModel()) { qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set"; return; } const std::vector<Key> selectedKeys = m_proxy->keys(m_view->selectionModel()->selectedRows()); const Key currentKey = m_proxy->key(m_view->currentIndex()); m_isHierarchical = on; find_last_proxy(m_proxy)->setSourceModel(model()); if (on) { m_view->expandAll(); } selectKeys(selectedKeys); if (!currentKey.isNull()) { const QModelIndex currentIndex = m_proxy->index(currentKey); if (currentIndex.isValid()) { m_view->selectionModel()->setCurrentIndex(m_proxy->index(currentKey), QItemSelectionModel::NoUpdate); m_view->scrollTo(currentIndex); } } Q_EMIT hierarchicalChanged(on); } void KeyTreeView::setKeys(const std::vector<Key> &keys) { std::vector<Key> sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); m_keys = sorted; if (m_flatModel) { m_flatModel->setKeys(sorted); } if (m_hierarchicalModel) { m_hierarchicalModel->setKeys(sorted); } } void KeyTreeView::addKeysImpl(const std::vector<Key> &keys, bool select) { if (keys.empty()) { return; } if (m_keys.empty()) { setKeys(keys); return; } std::vector<Key> sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); std::vector<Key> newKeys = _detail::union_by_fpr(sorted, m_keys); m_keys.swap(newKeys); if (m_flatModel) { m_flatModel->addKeys(sorted); } if (m_hierarchicalModel) { m_hierarchicalModel->addKeys(sorted); } if (select) { selectKeys(sorted); } } void KeyTreeView::addKeysSelected(const std::vector<Key> &keys) { addKeysImpl(keys, true); } void KeyTreeView::addKeysUnselected(const std::vector<Key> &keys) { addKeysImpl(keys, false); } void KeyTreeView::removeKeys(const std::vector<Key> &keys) { if (keys.empty()) { return; } std::vector<Key> sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); std::vector<Key> newKeys; newKeys.reserve(m_keys.size()); std::set_difference(m_keys.begin(), m_keys.end(), sorted.begin(), sorted.end(), std::back_inserter(newKeys), _detail::ByFingerprint<std::less>()); m_keys.swap(newKeys); if (m_flatModel) { std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) { m_flatModel->removeKey(key); }); } if (m_hierarchicalModel) { std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) { m_hierarchicalModel->removeKey(key); }); } } static const struct { const char *signal; const char *slot; } connections[] = { { SIGNAL(stringFilterChanged(QString)), SLOT(setStringFilter(QString)) }, { SIGNAL(keyFilterChanged(std::shared_ptr<Kleo::KeyFilter>)), SLOT(setKeyFilter(std::shared_ptr<Kleo::KeyFilter>)) }, }; static const unsigned int numConnections = sizeof connections / sizeof * connections; void KeyTreeView::disconnectSearchBar(const QObject *bar) { for (unsigned int i = 0; i < numConnections; ++i) { disconnect(this, connections[i].signal, bar, connections[i].slot); disconnect(bar, connections[i].signal, this, connections[i].slot); } } bool KeyTreeView::connectSearchBar(const QObject *bar) { for (unsigned int i = 0; i < numConnections; ++i) if (!connect(this, connections[i].signal, bar, connections[i].slot) || !connect(bar, connections[i].signal, this, connections[i].slot)) { return false; } return true; } void KeyTreeView::resizeColumns() { m_view->setColumnWidth(KeyListModelInterface::PrettyName, 260); m_view->setColumnWidth(KeyListModelInterface::PrettyEMail, 260); for (int i = 2; i < m_view->model()->columnCount(); ++i) { m_view->resizeColumnToContents(i); } } diff --git a/tests/test_uiserver.cpp b/tests/test_uiserver.cpp index 983569f80..615d0d1e3 100644 --- a/tests/test_uiserver.cpp +++ b/tests/test_uiserver.cpp @@ -1,298 +1,298 @@ /* -*- mode: c++; c-basic-offset:4 -*- tests/test_uiserver.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 */ // // Usage: test_uiserver <socket> --verify-detached <signed data> <signature> // #include <config-kleopatra.h> #include <kleo-assuan.h> #include <gpg-error.h> #include <Libkleo/KleoException> #include "utils/wsastarter.h" #include "utils/hex.h" #ifndef Q_OS_WIN32 # include <unistd.h> # include <sys/types.h> # include <sys/stat.h> # include <fcntl.h> # include <errno.h> #endif #include <cstdlib> #include <iostream> #include <map> #include <string> #include <vector> using namespace Kleo; #ifdef Q_OS_WIN32 static const bool HAVE_FD_PASSING = false; #else static const bool HAVE_FD_PASSING = true; #endif static const unsigned int ASSUAN_CONNECT_FLAGS = HAVE_FD_PASSING ? 1 : 0; static std::vector<int> inFDs, outFDs, msgFDs; static std::vector<std::string> inFiles, outFiles, msgFiles; static std::map<std::string, std::string> inquireData; static void usage(const std::string &msg = std::string()) { std::cerr << msg << std::endl << "\n" "Usage: test_uiserver <socket> [<io>] [<options>] [<inquire>] command [<args>]\n" "where:\n" #ifdef Q_OS_WIN32 " <io>: [--input[-fd] <file>] [--output[-fd] <file>] [--message[-fd] <file>]\n" #else " <io>: [--input <file>] [--output <file>] [--message <file>]\n" #endif " <options>: *[--option name=value]\n" " <inquire>: [--inquire keyword=<file>]\n"; exit(1); } #ifndef HAVE_ASSUAN2 static assuan_error_t data(void *void_ctx, const void *buffer, size_t len) { #else static gpg_error_t data(void *void_ctx, const void *buffer, size_t len) { #endif (void)void_ctx; (void)buffer; (void)len; return 0; // ### implement me } #ifndef HAVE_ASSUAN2 static assuan_error_t status(void *void_ctx, const char *line) { #else static gpg_error_t status(void *void_ctx, const char *line) { #endif (void)void_ctx; (void)line; return 0; } #ifndef HAVE_ASSUAN2 static assuan_error_t inquire(void *void_ctx, const char *keyword) { #else static gpg_error_t inquire(void *void_ctx, const char *keyword) { #endif assuan_context_t ctx = (assuan_context_t)void_ctx; Q_ASSERT(ctx); const std::map<std::string, std::string>::const_iterator it = inquireData.find(keyword); if (it == inquireData.end()) { return gpg_error(GPG_ERR_UNKNOWN_COMMAND); } if (!it->second.empty() && it->second[0] == '@') { return gpg_error(GPG_ERR_NOT_IMPLEMENTED); } if (const gpg_error_t err = assuan_send_data(ctx, it->second.c_str(), it->second.size())) { qDebug("assuan_write_data: %s", gpg_strerror(err)); return err; } return 0; } int main(int argc, char *argv[]) { const Kleo::WSAStarter _wsastarter; #ifndef HAVE_ASSUAN2 assuan_set_assuan_err_source(GPG_ERR_SOURCE_DEFAULT); #else assuan_set_gpg_err_source(GPG_ERR_SOURCE_DEFAULT); #endif if (argc < 3) { usage(); // need socket and command, at least } const char *socket = argv[1]; std::vector<const char *> options; std::string command; for (int optind = 2; optind < argc; ++optind) { const char *const arg = argv[optind]; if (qstrcmp(arg, "--input") == 0) { const std::string file = argv[++optind]; inFiles.push_back(file); } else if (qstrcmp(arg, "--output") == 0) { const std::string file = argv[++optind]; outFiles.push_back(file); } else if (qstrcmp(arg, "--message") == 0) { const std::string file = argv[++optind]; msgFiles.push_back(file); #ifndef Q_OS_WIN32 } else if (qstrcmp(arg, "--input-fd") == 0) { int inFD; if ((inFD = open(argv[++optind], O_RDONLY)) == -1) { perror("--input-fd open()"); return 1; } inFDs.push_back(inFD); } else if (qstrcmp(arg, "--output-fd") == 0) { int outFD; if ((outFD = open(argv[++optind], O_WRONLY | O_CREAT, 0666)) == -1) { perror("--output-fd open()"); return 1; } outFDs.push_back(outFD); } else if (qstrcmp(arg, "--message-fd") == 0) { int msgFD; if ((msgFD = open(argv[++optind], O_RDONLY)) == -1) { perror("--message-fd open()"); return 1; } msgFDs.push_back(msgFD); #endif } else if (qstrcmp(arg, "--option") == 0) { options.push_back(argv[++optind]); } else if (qstrcmp(arg, "--inquire") == 0) { const std::string inqval = argv[++optind]; const size_t pos = inqval.find('='); // ### implement indirection with "@file"... inquireData[inqval.substr(0, pos)] = inqval.substr(pos + 1); } else { while (optind < argc) { if (!command.empty()) { command += ' '; } command += argv[optind++]; } } } if (command.empty()) { usage("Command expected, but only options found"); } assuan_context_t ctx = nullptr; #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_socket_connect_ext(&ctx, socket, -1, ASSUAN_CONNECT_FLAGS)) { qDebug("%s", Exception(err, "assuan_socket_connect_ext").what()); #else if (const gpg_error_t err = assuan_new(&ctx)) { qDebug("%s", Exception(err, "assuan_new").what()); return 1; } if (const gpg_error_t err = assuan_socket_connect(ctx, socket, -1, ASSUAN_CONNECT_FLAGS)) { qDebug("%s", Exception(err, "assuan_socket_connect").what()); #endif return 1; } assuan_set_log_stream(ctx, stderr); #ifndef Q_OS_WIN32 for (std::vector<int>::const_iterator it = inFDs.begin(), end = inFDs.end(); it != end; ++it) { if (const gpg_error_t err = assuan_sendfd(ctx, *it)) { qDebug("%s", Exception(err, "assuan_sendfd( inFD )").what()); return 1; } if (const gpg_error_t err = assuan_transact(ctx, "INPUT FD", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, "INPUT FD").what()); return 1; } } for (std::vector<int>::const_iterator it = msgFDs.begin(), end = msgFDs.end(); it != end; ++it) { if (const gpg_error_t err = assuan_sendfd(ctx, *it)) { qDebug("%s", Exception(err, "assuan_sendfd( msgFD )").what()); return 1; } if (const gpg_error_t err = assuan_transact(ctx, "MESSAGE FD", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, "MESSAGE FD").what()); return 1; } } for (std::vector<int>::const_iterator it = outFDs.begin(), end = outFDs.end(); it != end; ++it) { if (const gpg_error_t err = assuan_sendfd(ctx, *it)) { qDebug("%s", Exception(err, "assuan_sendfd( outFD )").what()); return 1; } if (const gpg_error_t err = assuan_transact(ctx, "OUTPUT FD", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, "OUTPUT FD").what()); return 1; } } #endif for (std::vector<std::string>::const_iterator it = inFiles.begin(), end = inFiles.end(); it != end; ++it) { char buffer[1024]; sprintf(buffer, "INPUT FILE=%s", hexencode(*it).c_str()); if (const gpg_error_t err = assuan_transact(ctx, buffer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, buffer).what()); return 1; } } for (std::vector<std::string>::const_iterator it = msgFiles.begin(), end = msgFiles.end(); it != end; ++it) { char buffer[1024]; sprintf(buffer, "MESSAGE FILE=%s", hexencode(*it).c_str()); if (const gpg_error_t err = assuan_transact(ctx, buffer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, buffer).what()); return 1; } } for (std::vector<std::string>::const_iterator it = outFiles.begin(), end = outFiles.end(); it != end; ++it) { char buffer[1024]; sprintf(buffer, "OUTPUT FILE=%s", hexencode(*it).c_str()); if (const gpg_error_t err = assuan_transact(ctx, buffer, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, buffer).what()); return 1; } } - Q_FOREACH (const char *opt, options) { + for (const char *opt : qAsConst(options)) { std::string line = "OPTION "; line += opt; if (const gpg_error_t err = assuan_transact(ctx, line.c_str(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { qDebug("%s", Exception(err, line).what()); return 1; } } if (const gpg_error_t err = assuan_transact(ctx, command.c_str(), data, ctx, inquire, ctx, status, ctx)) { qDebug("%s", Exception(err, command).what()); return 1; } #ifndef HAVE_ASSUAN2 assuan_disconnect(ctx); #else assuan_release(ctx); #endif return 0; }