diff --git a/src/commands/certifycertificatecommand.cpp b/src/commands/certifycertificatecommand.cpp index 1c0bbda6d..829035a4f 100644 --- a/src/commands/certifycertificatecommand.cpp +++ b/src/commands/certifycertificatecommand.cpp @@ -1,332 +1,332 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/signcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2019 g10code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "certifycertificatecommand.h" #include "newcertificatecommand.h" #include "command_p.h" #include "exportopenpgpcertstoservercommand.h" #include "dialogs/certifycertificatedialog.h" #include "utils/tags.h" #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_REMARKS #endif using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; using namespace QGpgME; class CertifyCertificateCommand::Private : public Command::Private { friend class ::Kleo::Commands::CertifyCertificateCommand; CertifyCertificateCommand *q_func() const { return static_cast(q); } public: explicit Private(CertifyCertificateCommand *qq, KeyListController *c); ~Private(); void init(); private: void slotDialogRejected(); void slotResult(const Error &err); void slotCertificationPrepared(); private: void ensureDialogCreated(); void createJob(); private: std::vector uids; QPointer dialog; QPointer job; }; CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func() { return static_cast(d.get()); } const CertifyCertificateCommand::Private *CertifyCertificateCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() CertifyCertificateCommand::Private::Private(CertifyCertificateCommand *qq, KeyListController *c) : Command::Private(qq, c), uids(), dialog(), job() { } CertifyCertificateCommand::Private::~Private() { qCDebug(KLEOPATRA_LOG); if (dialog) { delete dialog; dialog = nullptr; } } CertifyCertificateCommand::CertifyCertificateCommand(KeyListController *c) : Command(new Private(this, c)) { d->init(); } CertifyCertificateCommand::CertifyCertificateCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { d->init(); } CertifyCertificateCommand::CertifyCertificateCommand(const GpgME::Key &key) : Command(key, new Private(this, nullptr)) { d->init(); } CertifyCertificateCommand::CertifyCertificateCommand(const GpgME::UserID &uid) : Command(uid.parent(), new Private(this, nullptr)) { std::vector(1, uid).swap(d->uids); d->init(); } CertifyCertificateCommand::CertifyCertificateCommand(const std::vector &uids) : Command(uids.empty() ? Key() : uids.front().parent(), new Private(this, nullptr)) { d->uids = uids; d->init(); } void CertifyCertificateCommand::Private::init() { } CertifyCertificateCommand::~CertifyCertificateCommand() { qCDebug(KLEOPATRA_LOG); } void CertifyCertificateCommand::doStart() { const std::vector keys = d->keys(); if (keys.size() != 1 || keys.front().protocol() != GpgME::OpenPGP) { d->finished(); return; } std::vector secKeys; Q_FOREACH (const Key &secKey, KeyCache::instance()->secretKeys()) { // Only include usable keys. if (secKey.canCertify() && secKey.protocol() == OpenPGP && !secKey.isRevoked() && !secKey.isExpired() && !secKey.isInvalid()) { secKeys.push_back(secKey); } } if (secKeys.empty()) { auto sel = KMessageBox::questionYesNo(d->parentWidgetOrView(), xi18nc("@info", "To certify other certificates, you first need to create an OpenPGP certificate for yourself.") + QStringLiteral("

") + i18n("Do you wish to create one now?"), i18n("Certification Not Possible")); if (sel == KMessageBox::Yes) { QEventLoop loop; auto cmd = new Commands::NewCertificateCommand(); cmd->setParentWidget(d->parentWidgetOrView()); cmd->setProtocol(GpgME::OpenPGP); loop.connect(cmd, SIGNAL(finished()), SLOT(quit())); QMetaObject::invokeMethod(cmd, &Commands::NewCertificateCommand::start, Qt::QueuedConnection); loop.exec(); } else { Q_EMIT(canceled()); d->finished(); return; } Q_FOREACH (const Key &secKey, KeyCache::instance()->secretKeys()) { // Check again for secret keys if (secKey.canCertify() && secKey.protocol() == OpenPGP && !secKey.isRevoked() && !secKey.isExpired() && !secKey.isInvalid()) { secKeys.push_back(secKey); } } if (secKeys.empty()) { qCDebug(KLEOPATRA_LOG) << "Sec Keys still empty after keygen."; Q_EMIT(canceled()); d->finished(); return; } } const Key &key = keys.front(); - for (const UserID &uid : qAsConst(d->uids)) + for (const UserID &uid : std::as_const(d->uids)) if (qstricmp(uid.parent().primaryFingerprint(), key.primaryFingerprint()) != 0) { qCWarning(KLEOPATRA_LOG) << "User-ID <-> Key mismatch!"; d->finished(); return; } d->ensureDialogCreated(); Q_ASSERT(d->dialog); Key target = d->key(); #ifdef GPGME_HAS_REMARKS if (!(target.keyListMode() & GpgME::SignatureNotations)) { target.update(); } #endif d->dialog->setCertificateToCertify(target); if (d->uids.size()) { d->dialog->setSelectedUserIDs(d->uids); } d->dialog->show(); } void CertifyCertificateCommand::Private::slotDialogRejected() { Q_EMIT q->canceled(); finished(); } void CertifyCertificateCommand::Private::slotResult(const Error &err) { if (!err && !err.isCanceled() && dialog && dialog->exportableCertificationSelected() && dialog->sendToServer()) { auto const cmd = new ExportOpenPGPCertsToServerCommand(key()); cmd->start(); } else if (!err) { information(i18n("Certification successful."), i18n("Certification Succeeded")); } else { error(i18n("

An error occurred while trying to certify

" "%1:

\t%2

", Formatting::formatForComboBox(key()), QString::fromUtf8(err.asString())), i18n("Certification Error")); } if (!dialog->tags().isEmpty()) { Tags::enableTags(); } finished(); } void CertifyCertificateCommand::Private::slotCertificationPrepared() { Q_ASSERT(dialog); createJob(); Q_ASSERT(job); job->setExportable(dialog->exportableCertificationSelected()); job->setNonRevocable(dialog->nonRevocableCertificationSelected()); job->setUserIDsToSign(dialog->selectedUserIDs()); job->setSigningKey(dialog->selectedSecretKey()); job->setCheckLevel(dialog->selectedCheckLevel()); #ifdef GPGME_HAS_REMARKS if (!dialog->tags().isEmpty()) { // do not set an empty remark to avoid an empty signature notation (GnuPG bug T5142) job->setRemark(dialog->tags()); } // This also came with 1.14.0 job->setDupeOk(true); #endif #ifdef QGPGME_SUPPORTS_TRUST_SIGNATURES if (dialog->trustSignatureSelected() && !dialog->trustSignatureDomain().isEmpty()) { // always create level 1 trust signatures with complete trust job->setTrustSignature(TrustSignatureTrust::Complete, 1, dialog->trustSignatureDomain()); } #endif #ifdef QGPGME_SUPPORTS_SIGNATURE_EXPIRATION if (!dialog->expirationDate().isNull()) { job->setExpirationDate(dialog->expirationDate()); } #endif if (const Error err = job->start(key())) { slotResult(err); } } void CertifyCertificateCommand::doCancel() { qCDebug(KLEOPATRA_LOG); if (d->job) { d->job->slotCancel(); } } void CertifyCertificateCommand::Private::ensureDialogCreated() { if (dialog) { return; } dialog = new CertifyCertificateDialog; applyWindowID(dialog); connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected())); connect(dialog, SIGNAL(accepted()), q, SLOT(slotCertificationPrepared())); } void CertifyCertificateCommand::Private::createJob() { Q_ASSERT(!job); Q_ASSERT(key().protocol() == OpenPGP); const auto backend = QGpgME::openpgp(); if (!backend) { return; } SignKeyJob *const j = backend->signKeyJob(); if (!j) { return; } connect(j, &Job::progress, q, &Command::progress); connect(j, SIGNAL(result(GpgME::Error)), q, SLOT(slotResult(GpgME::Error))); job = j; } #undef d #undef q #include "moc_certifycertificatecommand.cpp" diff --git a/src/commands/changeroottrustcommand.cpp b/src/commands/changeroottrustcommand.cpp index b665f4c28..3371fd40d 100644 --- a/src/commands/changeroottrustcommand.cpp +++ b/src/commands/changeroottrustcommand.cpp @@ -1,360 +1,360 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/changeroottrustcommand.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 #include "changeroottrustcommand.h" #include "command_p.h" #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; class ChangeRootTrustCommand::Private : public QThread, public Command::Private { Q_OBJECT private: friend class ::Kleo::Commands::ChangeRootTrustCommand; ChangeRootTrustCommand *q_func() const { return static_cast(q); } public: explicit Private(ChangeRootTrustCommand *qq, KeyListController *c) : QThread(), Command::Private(qq, c), mutex(), trust(Key::Ultimate), trustListFile(QDir(gnupgHomeDirectory()).absoluteFilePath(QStringLiteral("trustlist.txt"))), canceled(false) { } private: void init() { q->setWarnWhenRunningAtShutdown(false); connect(this, SIGNAL(finished()), q_func(), SLOT(slotOperationFinished())); } void run() override; private: void slotOperationFinished() { KeyCache::mutableInstance()->enableFileSystemWatcher(true); if (error.isEmpty()) { KeyCache::mutableInstance()->reload(GpgME::CMS); } else Command::Private::error(i18n("Failed to update the trust database:\n" "%1", error), i18n("Root Trust Update Failed")); Command::Private::finished(); } private: mutable QMutex mutex; Key::OwnerTrust trust; QString trustListFile; QString gpgConfPath; QString error; volatile bool canceled; }; ChangeRootTrustCommand::Private *ChangeRootTrustCommand::d_func() { return static_cast(d.get()); } const ChangeRootTrustCommand::Private *ChangeRootTrustCommand::d_func() const { return static_cast(d.get()); } #define q q_func() #define d d_func() ChangeRootTrustCommand::ChangeRootTrustCommand(KeyListController *p) : Command(new Private(this, p)) { d->init(); } ChangeRootTrustCommand::ChangeRootTrustCommand(QAbstractItemView *v, KeyListController *p) : Command(v, new Private(this, p)) { d->init(); } ChangeRootTrustCommand::ChangeRootTrustCommand(const GpgME::Key &key, KeyListController *p) : Command(new Private(this, p)) { Q_ASSERT(!key.isNull()); d->init(); setKey(key); } ChangeRootTrustCommand::ChangeRootTrustCommand(const GpgME::Key &key, QAbstractItemView *v, KeyListController *p) : Command(v, new Private(this, p)) { Q_ASSERT(!key.isNull()); d->init(); setKey(key); } ChangeRootTrustCommand::~ChangeRootTrustCommand() {} void ChangeRootTrustCommand::setTrust(Key::OwnerTrust trust) { Q_ASSERT(!d->isRunning()); const QMutexLocker locker(&d->mutex); d->trust = trust; } Key::OwnerTrust ChangeRootTrustCommand::trust() const { const QMutexLocker locker(&d->mutex); return d->trust; } void ChangeRootTrustCommand::setTrustListFile(const QString &trustListFile) { Q_ASSERT(!d->isRunning()); const QMutexLocker locker(&d->mutex); d->trustListFile = trustListFile; } QString ChangeRootTrustCommand::trustListFile() const { const QMutexLocker locker(&d->mutex); return d->trustListFile; } void ChangeRootTrustCommand::doStart() { const std::vector keys = d->keys(); Key key; if (keys.size() == 1) { key = keys.front(); } else { qCWarning(KLEOPATRA_LOG) << "can only work with one certificate at a time"; } if (key.isNull()) { d->Command::Private::finished(); return; } d->gpgConfPath = gpgConfPath(); KeyCache::mutableInstance()->enableFileSystemWatcher(false); d->start(); } void ChangeRootTrustCommand::doCancel() { const QMutexLocker locker(&d->mutex); d->canceled = true; } static QString change_trust_file(const QString &trustListFile, const QString &key, Key::OwnerTrust trust); static QString run_gpgconf_reload_gpg_agent(const QString &gpgConfPath); void ChangeRootTrustCommand::Private::run() { QMutexLocker locker(&mutex); const QString key = QString::fromLatin1(keys().front().primaryFingerprint()); const Key::OwnerTrust trust = this->trust; const QString trustListFile = this->trustListFile; const QString gpgConfPath = this->gpgConfPath; locker.unlock(); QString err = change_trust_file(trustListFile, key, trust); if (err.isEmpty()) { err = run_gpgconf_reload_gpg_agent(gpgConfPath); } locker.relock(); this->error = err; } static QString add_colons(const QString &fpr) { QString result; result.reserve(fpr.size() / 2 * 3 + 1); bool needColon = false; for (QChar ch : fpr) { result += ch; if (needColon) { result += QLatin1Char(':'); } needColon = !needColon; } if (result.endsWith(QLatin1Char(':'))) { result.chop(1); } return result; } namespace { // fix stupid default-finalize behaviour... class KFixedSaveFile : public QSaveFile { public: explicit KFixedSaveFile(const QString &fileName) : QSaveFile(fileName) {} ~KFixedSaveFile() { cancelWriting(); } }; } // static QString change_trust_file(const QString &trustListFile, const QString &key, Key::OwnerTrust trust) { QList trustListFileContents; { QFile in(trustListFile); if (in.exists()) { // non-existence is not fatal... if (in.open(QIODevice::ReadOnly)) { trustListFileContents = in.readAll().split('\n'); } else { // ...but failure to open an existing file _is_ return i18n("Cannot open existing file \"%1\" for reading: %2", trustListFile, in.errorString()); } } // close, so KSaveFile doesn't clobber the original } KFixedSaveFile out(trustListFile); if (!out.open(QIODevice::WriteOnly)) return i18n("Cannot open file \"%1\" for reading and writing: %2", out.fileName() /*sic!*/, out.errorString()); if (!out.setPermissions(QFile::ReadOwner | QFile::WriteOwner)) return i18n("Cannot set restrictive permissions on file %1: %2", out.fileName() /*sic!*/, out.errorString()); const QString keyColon = add_colons(key); qCDebug(KLEOPATRA_LOG) << qPrintable(key) << " -> " << qPrintable(keyColon); // ( 1) ( 2 ) ( 3 )( 4) QRegExp rx(QLatin1String("\\s*(!?)\\s*([a-fA-F0-9]{40}|(?:[a-fA-F0-9]{2}:){19}[a-fA-F0-9]{2})\\s*([SsPp*])(.*)")); bool found = false; - for (const QByteArray &rawLine : qAsConst(trustListFileContents)) { + for (const QByteArray &rawLine : std::as_const(trustListFileContents)) { const QString line = QString::fromLatin1(rawLine.data(), rawLine.size()); if (!rx.exactMatch(line)) { qCDebug(KLEOPATRA_LOG) << "line \"" << rawLine.data() << "\" does not match"; out.write(rawLine + '\n'); continue; } const QString cap2 = rx.cap(2); if (cap2 != key && cap2 != keyColon) { qCDebug(KLEOPATRA_LOG) << qPrintable(key) << " != " << qPrintable(cap2) << " != " << qPrintable(keyColon); out.write(rawLine + '\n'); continue; } found = true; const bool disabled = rx.cap(1) == QLatin1Char('!'); const QByteArray flags = rx.cap(3).toLatin1(); const QByteArray rests = rx.cap(4).toLatin1(); if (trust == Key::Ultimate) if (!disabled) { // unchanged out.write(rawLine + '\n'); } else { out.write(keyColon.toLatin1() + ' ' + flags + rests + '\n'); } else if (trust == Key::Never) { if (disabled) { // unchanged out.write(rawLine + '\n'); } else { out.write('!' + keyColon.toLatin1() + ' ' + flags + rests + '\n'); } } // else: trust == Key::Unknown // -> don't write - ie.erase } if (!found) { // add if (trust == Key::Ultimate) { out.write(keyColon.toLatin1() + ' ' + 'S' + '\n'); } else if (trust == Key::Never) { out.write('!' + keyColon.toLatin1() + ' ' + 'S' + '\n'); } } if (!out.commit()) return i18n("Failed to move file %1 to its final destination, %2: %3", out.fileName(), trustListFile, out.errorString()); return QString(); } // static QString run_gpgconf_reload_gpg_agent(const QString &gpgConfPath) { if (gpgConfPath.isEmpty()) { return i18n("Could not find gpgconf executable"); } QProcess p; p.start(gpgConfPath, QStringList() << QStringLiteral("--reload") << QStringLiteral("gpg-agent")); qCDebug(KLEOPATRA_LOG) << "starting " << qPrintable(gpgConfPath) << " --reload gpg-agent"; p.waitForFinished(-1); qCDebug(KLEOPATRA_LOG) << "done"; if (p.error() == QProcess::UnknownError) { return QString(); } else { return i18n("\"gpgconf --reload gpg-agent\" failed: %1", p.errorString()); } } #undef q_func #undef d_func #include "moc_changeroottrustcommand.cpp" #include "changeroottrustcommand.moc" diff --git a/src/commands/importcertificatefromfilecommand.cpp b/src/commands/importcertificatefromfilecommand.cpp index c1d7285ec..7c26ba294 100644 --- a/src/commands/importcertificatefromfilecommand.cpp +++ b/src/commands/importcertificatefromfilecommand.cpp @@ -1,173 +1,173 @@ /* -*- mode: c++; c-basic-offset:4 -*- importcertificatefromfilecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "importcertificatefromfilecommand.h" #include "importcertificatescommand_p.h" #include "utils/filedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace QGpgME; class ImportCertificateFromFileCommand::Private : public ImportCertificatesCommand::Private { friend class ::ImportCertificateFromFileCommand; ImportCertificateFromFileCommand *q_func() const { return static_cast(q); } public: explicit Private(ImportCertificateFromFileCommand *qq, KeyListController *c); ~Private(); bool ensureHaveFile(); private: QStringList files; }; ImportCertificateFromFileCommand::Private *ImportCertificateFromFileCommand::d_func() { return static_cast(d.get()); } const ImportCertificateFromFileCommand::Private *ImportCertificateFromFileCommand::d_func() const { return static_cast(d.get()); } ImportCertificateFromFileCommand::Private::Private(ImportCertificateFromFileCommand *qq, KeyListController *c) : ImportCertificatesCommand::Private(qq, c), files() { } ImportCertificateFromFileCommand::Private::~Private() {} #define d d_func() #define q q_func() ImportCertificateFromFileCommand::ImportCertificateFromFileCommand() : ImportCertificatesCommand(new Private(this, nullptr)) { } ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(KeyListController *p) : ImportCertificatesCommand(new Private(this, p)) { } ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(QAbstractItemView *v, KeyListController *p) : ImportCertificatesCommand(v, new Private(this, p)) { } ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(const QStringList &files, KeyListController *p) : ImportCertificatesCommand(new Private(this, p)) { d->files = files; } ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(const QStringList &files, QAbstractItemView *v, KeyListController *p) : ImportCertificatesCommand(v, new Private(this, p)) { d->files = files; } ImportCertificateFromFileCommand::~ImportCertificateFromFileCommand() {} void ImportCertificateFromFileCommand::setFiles(const QStringList &files) { d->files = files; } void ImportCertificateFromFileCommand::doStart() { if (!d->ensureHaveFile()) { Q_EMIT canceled(); d->finished(); return; } //TODO: use KIO here d->setWaitForMoreJobs(true); - for (const QString &fn : qAsConst(d->files)) { + for (const QString &fn : std::as_const(d->files)) { QFile in(fn); if (!in.open(QIODevice::ReadOnly)) { d->error(i18n("Could not open file %1 for reading: %2", in.fileName(), in.errorString()), i18n("Certificate Import Failed")); d->importResult(ImportResult(), fn); continue; } const GpgME::Protocol protocol = findProtocol(fn); if (protocol == GpgME::UnknownProtocol) { //TODO: might use exceptions here d->error(i18n("Could not determine certificate type of %1.", in.fileName()), i18n("Certificate Import Failed")); d->importResult(ImportResult(), fn); continue; } d->startImport(protocol, in.readAll(), fn); } d->setWaitForMoreJobs(false); } static QStringList get_file_name(QWidget *parent) { const QString certificateFilter = i18n("Certificates") + QLatin1String(" (*.asc *.cer *.cert *.crt *.der *.pem *.gpg *.p7c *.p12 *.pfx *.pgp)"); const QString anyFilesFilter = i18n("Any files") + QLatin1String(" (*)"); QString previousDir; if (const KSharedConfig::Ptr config = KSharedConfig::openConfig()) { const KConfigGroup group(config, "Import Certificate"); previousDir = group.readPathEntry("last-open-file-directory", QDir::homePath()); } const QStringList files = Kleo::FileDialog::getOpenFileNames(parent, i18n("Select Certificate File"), previousDir, certificateFilter + QLatin1String(";;") + anyFilesFilter); if (!files.empty()) if (const KSharedConfig::Ptr config = KSharedConfig::openConfig()) { KConfigGroup group(config, "Import Certificate"); group.writePathEntry("last-open-file-directory", QFileInfo(files.front()).path()); } return files; } bool ImportCertificateFromFileCommand::Private::ensureHaveFile() { if (files.empty()) { files = get_file_name(parentWidgetOrView()); } return !files.empty(); } #undef d #undef q diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp index 8919df0df..bba6d03f6 100644 --- a/src/commands/importcertificatescommand.cpp +++ b/src/commands/importcertificatescommand.cpp @@ -1,678 +1,678 @@ /* -*- 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 #include "importcertificatescommand.h" #include "importcertificatescommand_p.h" #include "certifycertificatecommand.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::escape #include #include #include #include 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 &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 &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(KeyList::FingerprintRole).toString(); // find information: const std::vector::const_iterator it = Kleo::binary_find(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint()); if (it == m_importsByFingerprint.end()) { return AbstractKeyListSortFilterProxyModel::data(index, role); } else { QStringList rv; const auto ids = m_idsByFingerprint[it->fingerprint()]; rv.reserve(ids.size()); std::copy(ids.cbegin(), ids.cend(), std::back_inserter(rv)); return Formatting::importMetaData(*it, rv); } } bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { // // 0. Keep parents of matching children: // const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); Q_ASSERT(index.isValid()); for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i) if (filterAcceptsRow(i, index)) { return true; } // // 1. Check that this is an imported key: // const QString fpr = index.data(KeyList::FingerprintRole).toString(); return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint()); } private: void updateFindCache(const std::vector &results, const QStringList &ids) { Q_ASSERT(results.size() == static_cast(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 imports = results[i].imports(); m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end()); const QString &id = ids[i]; for (std::vector::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()); } private: mutable std::vector m_importsByFingerprint; mutable std::map< const char *, std::set, ByImportFingerprint > m_idsByFingerprint; std::vector 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("
")); } 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:
%1", format_ids(ids)); } void ImportCertificatesCommand::Private::setImportResultProxyModel(const std::vector &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(parentWidgetOrView())) { tv->expandAll(); } } int sum(const std::vector &res, int (ImportResult::*fun)() const) { return kdtools::accumulate_transform(res.begin(), res.end(), std::mem_fn(fun), 0); } static QString make_report(const std::vector &res, const QString &id = QString()) { const KLocalizedString normalLine = ki18n("%1%2"); const KLocalizedString boldLine = ki18n("%1%2"); const KLocalizedString headerLine = ki18n("%1"); #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 not 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 &res, const QStringList &ids) { Q_ASSERT(res.size() == static_cast(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("

Detailed results of certificate import:

" "%1
", make_report(res)) : i18n("

Detailed results of importing %1:

" "%2
", ids.front(), make_report(res)); return i18n("

Detailed results of certificate import:

" "%1
", 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("
") + i18n("Certifying means that you check the Fingerprint.") + QStringLiteral("
") + i18n("Some suggestions to do this are:") + QStringLiteral("
    • %1").arg(suggestions.join(QStringLiteral("
      "))) + QStringLiteral("
  • ") + 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 &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 &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("

    An error occurred while trying " "to import the certificate:

    " "

    %1

    ", QString::fromLocal8Bit(err.asString())) : i18n("

    An error occurred while trying " "to import the certificate %1:

    " "

    %2

    ", 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 &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 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", "You have imported a Secret Key." "The key has the fingerprint:" "%1" "" "And claims the User IDs:" "%2" "" "Is this your own key? (Set trust level to ultimate)", QString::fromUtf8(fingerPr), uids.join(QLatin1String(""))); 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. - for (const ImportResult &result : qAsConst(results)) { + for (const ImportResult &result : std::as_const(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,QString,GpgME::Error)), q, SLOT(keyListDone(GpgME::KeyListResult,std::vector,QString,GpgME::Error))); job->start(fingerprints, false); } void ImportCertificatesCommand::Private::keyListDone(const GpgME::KeyListResult &, const std::vector &keys, const QString &, const GpgME::Error&) { KeyCache::mutableInstance()->refresh(keys); showDetails(results, ids); auto tv = dynamic_cast (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 get_import_job(GpgME::Protocol protocol) { Q_ASSERT(protocol != UnknownProtocol); if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) { return std::unique_ptr(backend->importJob()); } else { return std::unique_ptr(); } } void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const QByteArray &data, const QString &id) { Q_ASSERT(protocol != UnknownProtocol); if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) { return; } std::unique_ptr job = get_import_job(protocol); if (!job.get()) { nonWorkingProtocols.push_back(protocol); error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.", Formatting::displayName(protocol)), i18n("Certificate Import Failed")); 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 get_import_from_keyserver_job(GpgME::Protocol protocol) { Q_ASSERT(protocol != UnknownProtocol); if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) { return std::unique_ptr(backend->importFromKeyserverJob()); } else { return std::unique_ptr(); } } void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const std::vector &keys, const QString &id) { Q_ASSERT(protocol != UnknownProtocol); if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) { return; } std::unique_ptr job = get_import_from_keyserver_job(protocol); if (!job.get()) { nonWorkingProtocols.push_back(protocol); error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.", Formatting::displayName(protocol)), i18n("Certificate Import Failed")); 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/commands/revokecertificationcommand.cpp b/src/commands/revokecertificationcommand.cpp index a77ff69c0..c56a7a1f8 100644 --- a/src/commands/revokecertificationcommand.cpp +++ b/src/commands/revokecertificationcommand.cpp @@ -1,254 +1,254 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/revokecertificationcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "revokecertificationcommand.h" #include "command_p.h" #include "exportopenpgpcertstoservercommand.h" #include "dialogs/revokecertificationdialog.h" #include #include #include #include #include #include #include "kleopatra_debug.h" #include #if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 # define GPGME_HAS_REVSIG #endif using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; using namespace QGpgME; class RevokeCertificationCommand::Private : public Command::Private { friend class ::Kleo::Commands::RevokeCertificationCommand; RevokeCertificationCommand *q_func() const { return static_cast(q); } public: explicit Private(RevokeCertificationCommand *qq, KeyListController *c); ~Private(); void init(); private: void slotDialogAccepted(); void slotDialogRejected(); void slotResult(const Error &err); private: void ensureDialogCreated(); void createJob(); private: Key certificationKey; Key certificationTarget; std::vector uids; QPointer dialog; QPointer job; }; RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() { return static_cast(d.get()); } const RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() RevokeCertificationCommand::Private::Private(RevokeCertificationCommand *qq, KeyListController *c) : Command::Private(qq, c) { } RevokeCertificationCommand::Private::~Private() { } void RevokeCertificationCommand::Private::init() { const std::vector keys_ = keys(); if (keys_.size() != 1) { qCWarning(KLEOPATRA_LOG) << "RevokeCertificationCommand::Private::init: Expected exactly one key, but got" << keys_.size(); return; } if (keys_.front().protocol() != GpgME::OpenPGP) { qCWarning(KLEOPATRA_LOG) << "RevokeCertificationCommand::Private::init: Expected OpenPGP key, but got" << keys_.front().protocolAsString(); return; } certificationTarget = keys_.front(); } void RevokeCertificationCommand::Private::slotDialogAccepted() { createJob(); #ifdef GPGME_HAS_REVSIG job->startRevokeSignature(certificationTarget, dialog->selectedCertificationKey(), dialog->selectedUserIDs()); #endif } void RevokeCertificationCommand::Private::slotDialogRejected() { canceled(); } void RevokeCertificationCommand::Private::slotResult(const Error &err) { if (err.isCanceled()) { // do nothing } else if (err) { error(i18n("

    An error occurred while trying to revoke the certification of

    " "%1:

    \t%2

    ", Formatting::formatForComboBox(certificationTarget), QString::fromUtf8(err.asString())), i18n("Revocation Error")); } else { information(i18n("Revocation successful."), i18n("Revocation Succeeded")); if (dialog && dialog->sendToServer()) { auto const cmd = new ExportOpenPGPCertsToServerCommand(certificationTarget); cmd->start(); } } finished(); } void RevokeCertificationCommand::Private::ensureDialogCreated() { if (dialog) { return; } dialog = new RevokeCertificationDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, SIGNAL(accepted()), q, SLOT(slotDialogAccepted())); connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected())); } void RevokeCertificationCommand::Private::createJob() { Q_ASSERT(!job); Q_ASSERT(certificationTarget.protocol() == OpenPGP); const auto backend = QGpgME::openpgp(); if (!backend) { return; } QuickJob *const j = backend->quickJob(); if (!j) { return; } connect(j, &Job::progress, q, &Command::progress); connect(j, SIGNAL(result(GpgME::Error)), q, SLOT(slotResult(GpgME::Error))); job = j; } RevokeCertificationCommand::RevokeCertificationCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { d->init(); } RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID &uid) : Command(uid.parent(), new Private(this, nullptr)) { std::vector(1, uid).swap(d->uids); d->init(); } RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID::Signature &signature) : Command(signature.parent().parent(), new Private(this, nullptr)) { std::vector(1, signature.parent()).swap(d->uids); d->certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID()); d->init(); } RevokeCertificationCommand::~RevokeCertificationCommand() { qCDebug(KLEOPATRA_LOG) << "~RevokeCertificationCommand()"; } // static bool RevokeCertificationCommand::isSupported() { #ifdef GPGME_HAS_REVSIG return engineInfo(GpgEngine).engineVersion() >= "2.2.24"; #else return false; #endif } void RevokeCertificationCommand::doStart() { if (d->certificationTarget.isNull()) { d->finished(); return; } - for (const UserID &uid : qAsConst(d->uids)) + for (const UserID &uid : std::as_const(d->uids)) if (qstricmp(uid.parent().primaryFingerprint(), d->certificationTarget.primaryFingerprint()) != 0) { qCWarning(KLEOPATRA_LOG) << "User-ID <-> Key mismatch!"; d->finished(); return; } d->ensureDialogCreated(); Q_ASSERT(d->dialog); d->dialog->setCertificateToRevoke(d->certificationTarget); if (!d->uids.empty()) { d->dialog->setSelectedUserIDs(d->uids); } if (!d->certificationKey.isNull()) { d->dialog->setSelectedCertificationKey(d->certificationKey); } d->dialog->show(); } void RevokeCertificationCommand::doCancel() { qCDebug(KLEOPATRA_LOG) << "RevokeCertificationCommand::doCancel()"; if (d->job) { d->job->slotCancel(); } } #undef d #undef q #include "moc_revokecertificationcommand.cpp" diff --git a/src/crypto/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp index 33c88be2f..49a824408 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 #include "autodecryptverifyfilescontroller.h" #include "fileoperationspreferences.h" #include #include #include #include #include "commands/decryptverifyfilescommand.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include 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 > buildTasks(const QStringList &, QStringList &); struct CryptoFile { QString baseName; QString fileName; GpgME::Protocol protocol = GpgME::UnknownProtocol; int classification = 0; std::shared_ptr output; }; QVector 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 > m_results; std::vector > m_runnableTasks, m_completedTasks; std::shared_ptr 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(); } void AutoDecryptVerifyFilesController::Private::slotDialogCanceled() { qCDebug(KLEOPATRA_LOG); } void AutoDecryptVerifyFilesController::Private::schedule() { if (!m_runningTask && !m_runnableTasks.empty()) { const std::shared_ptr 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 &i : qAsConst(m_results)) { + for (const std::shared_ptr &i : std::as_const(m_results)) { Q_EMIT q->verificationResult(i->verificationResult()); } } } void AutoDecryptVerifyFilesController::Private::exec() { Q_ASSERT(!m_dialog); QStringList undetected; std::vector > 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." "You can manually select what to do with the files now." "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 coll(new TaskCollection); - for (const std::shared_ptr &i : qAsConst(m_runnableTasks)) { + for (const std::shared_ptr &i : std::as_const(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 %1 to %2.", 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 %1 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 %1.", outpath)); continue; } } if (!QFile::rename(inpath, outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move %1 to %2.", inpath, outpath)); } } } q->emitDoneOrError(); delete m_dialog; m_dialog = nullptr; } QVector AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files) { const auto isSignature = [](int classification) -> bool { return mayBeDetachedSignature(classification) || mayBeOpaqueSignature(classification) || (classification & Class::TypeMask) == Class::ClearsignedMessage; }; QVector 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 > AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected) { // sort files so that we make sure we first decrypt and then verify QVector cryptoFiles = classifyAndSortFiles(fileNames); std::vector > 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 %1 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 %1 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; 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 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 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 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 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 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 &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 &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 &dvr = std::dynamic_pointer_cast(result)) { d->m_results.push_back(dvr); } QTimer::singleShot(0, this, SLOT(schedule())); } #include "moc_autodecryptverifyfilescontroller.cpp" diff --git a/src/crypto/decryptverifyemailcontroller.cpp b/src/crypto/decryptverifyemailcontroller.cpp index c7bfd38a4..d0eee2dd2 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 #include "decryptverifyemailcontroller.h" #include "kleopatra_debug.h" #include "emailoperationspreferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 &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 > 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 > m_inputs, m_signedDatas; std::vector > m_outputs; unsigned int m_sessionId; QPointer m_wizard; std::vector > m_results; std::vector > m_runnableTasks, m_completedTasks; std::shared_ptr m_runningTask; bool m_silent; bool m_operationCompleted; DecryptVerifyOperation m_operation; Protocol m_protocol; VerificationMode m_verificationMode; std::vector 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(); } 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 &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 &dvr = std::dynamic_pointer_cast(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 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 &i : qAsConst(m_results)) { + for (const std::shared_ptr &i : std::as_const(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 void collectGarbage(C &c) { auto 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 > s_wizards; collectGarbage(s_wizards); qCDebug(KLEOPATRA_LOG) << "id = " << id; if (id != 0) { const auto it = s_wizards.find(id); if (it != s_wizards.end()) { Q_ASSERT(it->second && "This should have been garbage-collected"); return it->second; } } auto 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 > 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 > tasks; for (unsigned int i = 0; i < numInputs; ++i) { std::shared_ptr task; switch (m_operation) { case Decrypt: { std::shared_ptr 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 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 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 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 &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 coll(new TaskCollection); std::vector > tsks; Q_FOREACH (const std::shared_ptr &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) { d->m_inputs.resize(1, input); } void DecryptVerifyEMailController::setInputs(const std::vector > &inputs) { d->m_inputs = inputs; } void DecryptVerifyEMailController::setSignedData(const std::shared_ptr &data) { d->m_signedDatas.resize(1, data); } void DecryptVerifyEMailController::setSignedData(const std::vector > &data) { d->m_signedDatas = data; } void DecryptVerifyEMailController::setOutput(const std::shared_ptr &output) { d->m_outputs.resize(1, output); } void DecryptVerifyEMailController::setOutputs(const std::vector > &outputs) { d->m_outputs = outputs; } void DecryptVerifyEMailController::setInformativeSenders(const std::vector &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/decryptverifyfilescontroller.cpp b/src/crypto/decryptverifyfilescontroller.cpp index 55bebbbfd..dc5eb08a6 100644 --- a/src/crypto/decryptverifyfilescontroller.cpp +++ b/src/crypto/decryptverifyfilescontroller.cpp @@ -1,443 +1,443 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifyfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "decryptverifyfilescontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; class DecryptVerifyFilesController::Private { DecryptVerifyFilesController *const q; public: static std::shared_ptr taskFromOperationWidget(const DecryptVerifyOperationWidget *w, const QString &fileName, const QDir &outDir, const std::shared_ptr &overwritePolicy); explicit Private(DecryptVerifyFilesController *qq); void slotWizardOperationPrepared(); void slotWizardCanceled(); void schedule(); void prepareWizardFromPassedFiles(); std::vector > buildTasks(const QStringList &, const std::shared_ptr &); void ensureWizardCreated(); void ensureWizardVisible(); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void cancelAllTasks(); QStringList m_passedFiles, m_filesAfterPreparation; QPointer m_wizard; std::vector > m_results; std::vector > m_runnableTasks, m_completedTasks; std::shared_ptr m_runningTask; bool m_errorDetected; DecryptVerifyOperation m_operation; }; // static std::shared_ptr DecryptVerifyFilesController::Private::taskFromOperationWidget(const DecryptVerifyOperationWidget *w, const QString &fileName, const QDir &outDir, const std::shared_ptr &overwritePolicy) { kleo_assert(w); std::shared_ptr task; switch (w->mode()) { case DecryptVerifyOperationWidget::VerifyDetachedWithSignature: { std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(fileName)); t->setSignedData(Input::createFromFile(w->signedDataFileName())); task = t; kleo_assert(fileName == w->inputFileName()); } break; case DecryptVerifyOperationWidget::VerifyDetachedWithSignedData: { std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(w->inputFileName())); t->setSignedData(Input::createFromFile(fileName)); task = t; kleo_assert(fileName == w->signedDataFileName()); } break; case DecryptVerifyOperationWidget::DecryptVerifyOpaque: { const unsigned int classification = classify(fileName); qCDebug(KLEOPATRA_LOG) << "classified" << fileName << "as" << printableClassification(classification); const std::shared_ptr ad = w->selectedArchiveDefinition(); const Protocol proto = isOpenPGP(classification) ? OpenPGP : isCMS(classification) ? CMS : ad /* _needs_ the info */ ? throw Exception(gpg_error(GPG_ERR_CONFLICT), i18n("Cannot determine whether input data is OpenPGP or CMS")) : /* else we don't care */ UnknownProtocol; const std::shared_ptr input = Input::createFromFile(fileName); const std::shared_ptr output = ad ? ad->createOutputFromUnpackCommand(proto, fileName, outDir) : /*else*/ Output::createFromFile(outDir.absoluteFilePath(outputFileName(QFileInfo(fileName).fileName())), overwritePolicy); if (mayBeCipherText(classification)) { qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask"; std::shared_ptr t(new DecryptVerifyTask); t->setInput(input); t->setOutput(output); task = t; } else { qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask"; std::shared_ptr t(new VerifyOpaqueTask); t->setInput(input); t->setOutput(output); task = t; } kleo_assert(fileName == w->inputFileName()); } break; } task->autodetectProtocolFromInput(); return task; } DecryptVerifyFilesController::Private::Private(DecryptVerifyFilesController *qq) : q(qq), m_errorDetected(false), m_operation(DecryptVerify) { qRegisterMetaType(); } void DecryptVerifyFilesController::Private::slotWizardOperationPrepared() { ensureWizardCreated(); std::vector > tasks = buildTasks(m_filesAfterPreparation, std::shared_ptr(new OverwritePolicy(m_wizard))); if (tasks.empty()) { reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), i18n("No usable inputs found")); } kleo_assert(m_runnableTasks.empty()); m_runnableTasks.swap(tasks); std::shared_ptr coll(new TaskCollection); for (const auto &i: m_runnableTasks) { q->connectTask(i); } coll->setTasks(m_runnableTasks); m_wizard->setTaskCollection(coll); QTimer::singleShot(0, q, SLOT(schedule())); } void DecryptVerifyFilesController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG); } void DecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr &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 &dvr = std::dynamic_pointer_cast(result)) { d->m_results.push_back(dvr); } QTimer::singleShot(0, this, SLOT(schedule())); } void DecryptVerifyFilesController::Private::schedule() { if (!m_runningTask && !m_runnableTasks.empty()) { const std::shared_ptr t = m_runnableTasks.back(); m_runnableTasks.pop_back(); t->start(); m_runningTask = t; } if (!m_runningTask) { kleo_assert(m_runnableTasks.empty()); for (const auto &i: m_results) { Q_EMIT q->verificationResult(i->verificationResult()); } q->emitDoneOrError(); } } void DecryptVerifyFilesController::Private::ensureWizardCreated() { if (m_wizard) { return; } std::unique_ptr w(new DecryptVerifyFilesWizard); w->setWindowTitle(i18nc("@title:window", "Decrypt/Verify Files")); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection); connect(w.get(), SIGNAL(canceled()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); m_wizard = w.release(); } namespace { struct FindExtension : std::unary_function, bool> { const QString ext; const Protocol proto; FindExtension(const QString &ext, Protocol proto) : ext(ext), proto(proto) {} bool operator()(const std::shared_ptr &ad) const { qCDebug(KLEOPATRA_LOG) << " considering" << (ad ? ad->label() : QStringLiteral("")) << "for" << ext; bool result; if (proto == UnknownProtocol) { result = ad && (ad->extensions(OpenPGP).contains(ext, Qt::CaseInsensitive) || ad->extensions(CMS).contains(ext, Qt::CaseInsensitive)); } else { result = ad && ad->extensions(proto).contains(ext, Qt::CaseInsensitive); } qCDebug(KLEOPATRA_LOG) << (result ? " -> matches" : " -> doesn't match"); return result; } }; } std::shared_ptr DecryptVerifyFilesController::pick_archive_definition(GpgME::Protocol proto, const std::vector< std::shared_ptr > &ads, const QString &filename) { const QFileInfo fi(outputFileName(filename)); QString extension = fi.completeSuffix(); if (extension == QLatin1String("out")) { // added by outputFileName() -> useless return std::shared_ptr(); } if (extension.endsWith(QLatin1String(".out"))) { // added by outputFileName() -> remove extension.chop(4); } for (;;) { const auto it = std::find_if(ads.begin(), ads.end(), FindExtension(extension, proto)); if (it != ads.end()) { return *it; } const int idx = extension.indexOf(QLatin1Char('.')); if (idx < 0) { return std::shared_ptr(); } extension = extension.mid(idx + 1); } } void DecryptVerifyFilesController::Private::prepareWizardFromPassedFiles() { ensureWizardCreated(); const std::vector< std::shared_ptr > archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); unsigned int counter = 0; - for (const auto &fname: qAsConst(m_passedFiles)) { + for (const auto &fname: std::as_const(m_passedFiles)) { kleo_assert(!fname.isEmpty()); const unsigned int classification = classify(fname); const Protocol proto = findProtocol(classification); if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification) || mayBeDetachedSignature(classification)) { DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); const QString signedDataFileName = findSignedData(fname); // this breaks opaque signatures whose source files still // happen to exist in the same directory. Until we have // content-based classification, this is the most unlikely // case, so that's the case we break. ### FIXME remove when content-classify is done if (mayBeDetachedSignature(classification) && !signedDataFileName.isEmpty()) { op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature); } // ### end FIXME else if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification)) { op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname)); } else { op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature); } op->setInputFileName(fname); op->setSignedDataFileName(signedDataFileName); m_filesAfterPreparation << fname; } else { // probably the signed data file was selected: const QStringList signatures = findSignatures(fname); if (signatures.empty()) { // We are assuming this is a detached signature file, but // there were no signature files for it. Let's guess it's encrypted after all. // ### FIXME once we have a proper heuristic for this, this should move into // classify() and/or classifyContent() DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname)); op->setInputFileName(fname); m_filesAfterPreparation << fname; } else { for (const auto &s: signatures) { DecryptVerifyOperationWidget *op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignedData); op->setInputFileName(s); op->setSignedDataFileName(fname); m_filesAfterPreparation << fname; } } } } m_wizard->setOutputDirectory(heuristicBaseDirectory(m_passedFiles)); return; } std::vector< std::shared_ptr > DecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, const std::shared_ptr &overwritePolicy) { const bool useOutDir = m_wizard->useOutputDirectory(); const QFileInfo outDirInfo(m_wizard->outputDirectory()); kleo_assert(!useOutDir || outDirInfo.isDir()); const QDir outDir(outDirInfo.absoluteFilePath()); kleo_assert(!useOutDir || outDir.exists()); std::vector > tasks; for (int i = 0, end = fileNames.size(); i != end; ++i) try { const QDir fileDir = QFileInfo(fileNames[i]).absoluteDir(); kleo_assert(fileDir.exists()); tasks.push_back(taskFromOperationWidget(m_wizard->operationWidget(static_cast(i)), fileNames[i], useOutDir ? outDir : fileDir, overwritePolicy)); } catch (const GpgME::Exception &e) { tasks.push_back(Task::makeErrorTask(e.error().code(), QString::fromLocal8Bit(e.what()), fileNames[i])); } return tasks; } void DecryptVerifyFilesController::setFiles(const QStringList &files) { d->m_passedFiles = files; } void DecryptVerifyFilesController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(m_wizard); } DecryptVerifyFilesController::DecryptVerifyFilesController(QObject *parent) : Controller(parent), d(new Private(this)) { } DecryptVerifyFilesController::DecryptVerifyFilesController(const std::shared_ptr &ctx, QObject *parent) : Controller(ctx, parent), d(new Private(this)) { } DecryptVerifyFilesController::~DecryptVerifyFilesController() { qCDebug(KLEOPATRA_LOG); } void DecryptVerifyFilesController::start() { d->prepareWizardFromPassedFiles(); d->ensureWizardVisible(); } void DecryptVerifyFilesController::setOperation(DecryptVerifyOperation op) { d->m_operation = op; } DecryptVerifyOperation DecryptVerifyFilesController::operation() const { return d->m_operation; } void DecryptVerifyFilesController::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 DecryptVerifyFilesController::cancel() { qCDebug(KLEOPATRA_LOG); try { d->m_errorDetected = true; if (d->m_wizard) { d->m_wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } #include "moc_decryptverifyfilescontroller.cpp" diff --git a/src/crypto/encryptemailcontroller.cpp b/src/crypto/encryptemailcontroller.cpp index be544f910..463a87dfd 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 #include "encryptemailcontroller.h" #include "kleopatra_debug.h" #include "encryptemailtask.h" #include "taskcollection.h" #include #include #include #include #include #include #include "emailoperationspreferences.h" #include #include #include #include #include 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 takeRunnable(GpgME::Protocol proto); private: const Mode mode; std::vector< std::shared_ptr > runnable, completed; std::shared_ptr cms, openpgp; mutable QPointer wizard; }; EncryptEMailController::Private::Private(Mode m, EncryptEMailController *qq) : q(qq), mode(m), runnable(), cms(), openpgp(), wizard() { } EncryptEMailController::EncryptEMailController(const std::shared_ptr &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(), std::vector()); } void EncryptEMailController::startResolveRecipients(const std::vector &recipients, const std::vector &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, const std::shared_ptr &output) { setInputsAndOutputs(std::vector< std::shared_ptr >(1, input), std::vector< std::shared_ptr >(1, output)); } void EncryptEMailController::setInputsAndOutputs(const std::vector< std::shared_ptr > &inputs, const std::vector< std::shared_ptr > &outputs) { kleo_assert(!inputs.empty()); kleo_assert(outputs.size() == inputs.size()); std::vector< std::shared_ptr > tasks; tasks.reserve(inputs.size()); d->ensureWizardCreated(); const std::vector keys = d->wizard->resolvedCertificates(); kleo_assert(!keys.empty()); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr 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 coll(new TaskCollection); std::vector > tmp; std::copy(d->runnable.begin(), d->runnable.end(), std::back_inserter(tmp)); coll->setTasks(tmp); d->ensureWizardCreated(); d->wizard->setTaskCollection(coll); - for (const std::shared_ptr &t : qAsConst(tmp)) { + for (const std::shared_ptr &t : std::as_const(tmp)) { connectTask(t); } d->schedule(); } void EncryptEMailController::Private::schedule() { if (!cms) if (const std::shared_ptr t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (cms || openpgp) { return; } kleo_assert(runnable.empty()); q->emitDoneOrError(); } std::shared_ptr EncryptEMailController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr(); } const std::shared_ptr result = *it; runnable.erase(it); return result; } void EncryptEMailController::doTaskDone(const Task *task, const std::shared_ptr &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 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 455ed698b..06a64e118 100644 --- a/src/crypto/gui/resolverecipientspage.cpp +++ b/src/crypto/gui/resolverecipientspage.cpp @@ -1,695 +1,695 @@ /* -*- 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 #include "resolverecipientspage.h" #include "resolverecipientspage_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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); auto 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)); auto item = new QListWidgetItem; item->setData(IdRole, id); auto 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 &pgp, const std::vector &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; - for (ItemWidget *i : qAsConst(widgets)) { + for (ItemWidget *i : std::as_const(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 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); auto 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("No certificate selected")); 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(), std::vector()); } 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 *createCertificateSelectionDialog(QWidget *parent, GpgME::Protocol prot) { auto const dlg = new CertificateSelectionDialog(parent); const CertificateSelectionDialog::Options options = CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::EncryptOnly | CertificateSelectionDialog::MultiSelection | CertificateSelectionDialog::optionsFromProtocol(prot); dlg->setOptions(options); return dlg; } void ResolveRecipientsPage::ItemWidget::showSelectionDialog() { QPointer 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 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(); - for (const Key &i : qAsConst(certs)) { + for (const Key &i : std::as_const(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 &pgp, const std::vector &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 ResolveRecipientsPage::ItemWidget::certificates() const { std::vector 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 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("Recipients")); auto 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); auto buttonWidget = new QWidget; auto 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); auto protocolWidget = new QWidget; auto protocolLayout = new QHBoxLayout(protocolWidget); auto 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 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("Warning: 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 auto protocol = static_cast(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 dlg = createCertificateSelectionDialog(q, q->selectedProtocol()); if (dlg->exec() != QDialog::Accepted || !dlg /*q already deleted*/) { return; } const std::vector keys = dlg->selectedCertificates(); int i = 0; 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 pgp = key.protocol() == OpenPGP ? std::vector(1, key) : std::vector(); const std::vector cms = key.protocol() == CMS ? std::vector(1, key) : std::vector(); m_listWidget->setCertificates(rec, pgp, cms); } Q_EMIT q->completeChanged(); } namespace { std::vector makeSuggestions(const std::shared_ptr &prefs, const Mailbox &mb, GpgME::Protocol prot) { std::vector 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 &keys) { QStringList list; std::transform(keys.begin(), keys.end(), list.begin(), &Formatting::formatKeyLink); return list.join(QLatin1String("
    ")); } void ResolveRecipientsPage::setAdditionalRecipientsInfo(const std::vector &recipients) { d->m_additionalRecipientsLabel->setVisible(!recipients.empty()); if (recipients.empty()) { return; } d->m_additionalRecipientsLabel->setText( i18n("

    Recipients predefined via GnuPG settings:

    %1
    ", listKeysForInfo(recipients))); } void ResolveRecipientsPage::setRecipients(const std::vector &recipients, const std::vector &encryptToSelfRecipients) { uint cmsCount = 0; uint pgpCount = 0; uint senders = 0; for (const Mailbox &mb : encryptToSelfRecipients) { const QString id = QLatin1String("sender-") + QString::number(++senders); d->m_listWidget->addEntry(id, i18n("Sender"), mb); const std::vector pgp = makeSuggestions(d->m_recipientPreferences, mb, OpenPGP); const std::vector cms = makeSuggestions(d->m_recipientPreferences, mb, CMS); pgpCount += !pgp.empty(); cmsCount += !cms.empty(); d->m_listWidget->setCertificates(id, pgp, cms); } for (const Mailbox &i : recipients) { //TODO: const QString address = i.prettyAddress(); d->addRecipient(i); const std::vector pgp = makeSuggestions(d->m_recipientPreferences, i, OpenPGP); const std::vector 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 ResolveRecipientsPage::resolvedCertificates() const { std::vector 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 ResolveRecipientsPage::recipientPreferences() const { return d->m_recipientPreferences; } void ResolveRecipientsPage::setRecipientPreferences(const std::shared_ptr &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 22474745f..19e78ed47 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 #include "signencryptemailconflictdialog.h" #include #include #include "dialogs/certificateselectiondialog.h" #include "certificateselectionline.h" #include #include "utils/gui-helper.h" #include "utils/kleo_assert.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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) { auto 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; - for (const CertificateSelectionLine &line : qAsConst(ui.signers)) { + for (const CertificateSelectionLine &line : std::as_const(ui.signers)) { line.showHide(proto, first, showAll, sign); } ui.selectSigningCertificatesGB.setVisible(sign && (showAll || !first)); first = true; - for (const CertificateSelectionLine &line : qAsConst(ui.recipients)) { + for (const CertificateSelectionLine &line : std::as_const(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)) { + for (const Sender &s : std::as_const(senders)) { addSigner(s); } ui.addSelectEncryptionCertificatesGB(); - for (const Sender &s : qAsConst(senders)) { + for (const Sender &s : std::as_const(senders)) { addRecipient(s); } - for (const Recipient &r : qAsConst(recipients)) { + for (const Recipient &r : std::as_const(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 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 senders; std::vector 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 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 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 &pgp, bool pgpAmbiguous, const std::vector &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 &pgp, bool pgpAmbiguous, const std::vector &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 &senders) { if (senders == d->senders) { return; } d->senders = senders; d->createSendersAndRecipients(); d->showHideWidgets(); d->updateDialogStatus(); } void SignEncryptEMailConflictDialog::setRecipients(const std::vector &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 get_keys(const std::vector &lines, Protocol proto) { if (proto == UnknownProtocol) { return std::vector(); } Q_ASSERT(proto == OpenPGP || proto == CMS); std::vector 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 SignEncryptEMailConflictDialog::resolvedSigningKeys() const { return d->sign ? get_keys(d->ui.signers, selectedProtocol()) : std::vector(); } std::vector SignEncryptEMailConflictDialog::resolvedEncryptionKeys() const { return d->encrypt ? get_keys(d->ui.recipients, selectedProtocol()) : std::vector(); } 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/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp index d7dbc0c24..1d205e587 100644 --- a/src/crypto/gui/signencryptwidget.cpp +++ b/src/crypto/gui/signencryptwidget.cpp @@ -1,612 +1,612 @@ /* 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 "settings.h" #include "unknownrecipientwidget.h" #include "commands/detailscommand.h" #include "dialogs/certificateselectiondialog.h" #include "dialogs/groupdetailsdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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) { auto lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); mModel->useKeyCache(true, KeyList::IncludeGroups); /* The signature selection */ auto sigLay = new QHBoxLayout; auto 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); auto encBoxLay = new QVBoxLayout; auto 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) { - for (CertificateLineEdit *edit : qAsConst(mRecpWidgets)) { + for (CertificateLineEdit *edit : std::as_const(mRecpWidgets)) { edit->setEnabled(toggled); } updateOp(); }); // Scroll area for other keys auto recipientWidget = new QWidget; auto 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 less secure 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); addRecipientWidget(); updateOp(); } CertificateLineEdit *SignEncryptWidget::addRecipientWidget() { auto 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, [this] () { addRecipientWidget(); }); connect(certSel, &CertificateLineEdit::dialogRequested, this, [this, certSel] () { dialogRequested(certSel); }); return certSel; } void SignEncryptWidget::addRecipient(const Key &key) { CertificateLineEdit *certSel = addRecipientWidget(); if (!key.isNull()) { certSel->setKey(key); mAddedKeys << key; } } void SignEncryptWidget::addRecipient(const KeyGroup &group) { CertificateLineEdit *certSel = addRecipientWidget(); if (!group.isNull()) { certSel->setGroup(group); mAddedGroups << group; } } void SignEncryptWidget::dialogRequested(CertificateLineEdit *certificateLineEdit) { if (!certificateLineEdit->key().isNull()) { auto cmd = new Commands::DetailsCommand(certificateLineEdit->key(), nullptr); cmd->start(); return; } if (!certificateLineEdit->group().isNull()) { auto dlg = new GroupDetailsDialog; dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setGroup(certificateLineEdit->group()); dlg->show(); return; } auto const dlg = new CertificateSelectionDialog(this); dlg->setOptions(CertificateSelectionDialog::Options( CertificateSelectionDialog::MultiSelection | CertificateSelectionDialog::EncryptOnly | CertificateSelectionDialog::optionsFromProtocol(mCurrentProto) | CertificateSelectionDialog::IncludeGroups)); if (dlg->exec()) { const std::vector keys = dlg->selectedCertificates(); const std::vector groups = dlg->selectedGroups(); if (keys.size() == 0 && groups.size() == 0) { return; } bool isFirstItem = true; for (const Key &key : keys) { if (isFirstItem) { certificateLineEdit->setKey(key); isFirstItem = false; } else { addRecipient(key); } } for (const KeyGroup &group : groups) { if (isFirstItem) { certificateLineEdit->setGroup(group); isFirstItem = false; } else { addRecipient(group); } } } delete dlg; recipientsChanged(); } void SignEncryptWidget::clearAddedRecipients() { - for (auto w: qAsConst(mUnknownWidgets)) { + for (auto w: std::as_const(mUnknownWidgets)) { mRecpLayout->removeWidget(w); delete w; } - for (auto &key: qAsConst(mAddedKeys)) { + for (auto &key: std::as_const(mAddedKeys)) { removeRecipient(key); } - for (auto &group: qAsConst(mAddedGroups)) { + for (auto &group: std::as_const(mAddedGroups)) { removeRecipient(group); } } 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 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; - for (const CertificateLineEdit *w : qAsConst(mRecpWidgets)) { + for (const CertificateLineEdit *w : std::as_const(mRecpWidgets)) { if (w->key().isNull() && w->group().isNull()) { oneEmpty = true; break; } } if (!oneEmpty) { addRecipientWidget(); } 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(); } std::vector SignEncryptWidget::recipients() const { std::vector ret; - for (const CertificateLineEdit *w : qAsConst(mRecpWidgets)) { + for (const CertificateLineEdit *w : std::as_const(mRecpWidgets)) { if (!w->isEnabled()) { // If one is disabled, all are disabled. break; } const Key k = w->key(); const KeyGroup g = w->group(); if (!k.isNull()) { ret.push_back(k); } else if (!g.isNull()) { const auto keys = g.keys(); std::copy(keys.begin(), keys.end(), std::back_inserter(ret)); } } const Key k = selfKey(); if (!k.isNull()) { ret.push_back(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 std::vector recp = recipients(); QString newOp; if (!sigKey.isNull() && (!recp.empty() || encryptSymmetric())) { newOp = i18nc("@action", "Sign / Encrypt"); } else if (!recp.empty() || 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; - for (const CertificateLineEdit *edit : qAsConst(mRecpWidgets)) { + for (const CertificateLineEdit *edit : std::as_const(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)) { + for (CertificateLineEdit *edit: std::as_const(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; } } } void SignEncryptWidget::removeRecipient(const KeyGroup &group) { - for (CertificateLineEdit *edit: qAsConst(mRecpWidgets)) { + for (CertificateLineEdit *edit: std::as_const(mRecpWidgets)) { const auto editGroup = edit->group(); if (group.isNull() && editGroup.isNull()) { recpRemovalRequested(edit); return; } if (editGroup.name() == group.name()) { 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(new SignCertificateFilter(proto))); mSelfSelect->setKeyFilter(std::shared_ptr(new EncryptSelfCertificateFilter(proto))); const auto encFilter = std::shared_ptr(new EncryptCertificateFilter(proto)); - for (CertificateLineEdit *edit : qAsConst(mRecpWidgets)) { + for (CertificateLineEdit *edit : std::as_const(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)) { + for (const auto edit: std::as_const(mRecpWidgets)) { if (!edit->isEmpty() && edit->key().isNull() && edit->group().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 0ba88e301..cb91c859a 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 #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 #include "utils/input.h" #include "utils/output.h" #include #include "utils/kleo_assert.h" #include #include #include #include #include #include #include #include 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," : ",", 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," : ",", 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," : ",", qPrintable(recipient.mailbox().prettyAddress()), result); return result; } } static bool has_perfect_match(bool sign, bool encrypt, Protocol proto, const std::vector &senders, const std::vector &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 &senders, const std::vector &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 &senders, const std::vector &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 &senders, const std::vector &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 &senders, const std::vector &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 mailbox2sender(const std::vector &mbs) { std::vector senders; senders.reserve(mbs.size()); for (const Mailbox &mb : mbs) { senders.push_back(Sender(mb)); } return senders; } static std::vector mailbox2recipient(const std::vector &mbs) { std::vector 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 takeRunnable(GpgME::Protocol proto); private: bool sign : 1; bool encrypt : 1; bool resolvingInProgress : 1; bool certificatesResolved : 1; bool detached : 1; Protocol presetProtocol; std::vector signers, recipients; std::vector< std::shared_ptr > runnable, completed; std::shared_ptr cms, openpgp; QPointer 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 &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 &r, const std::vector &s) { d->certificatesResolved = false; d->resolvingInProgress = true; const std::vector senders = mailbox2sender(s); const std::vector 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 > &inputs, const std::vector< std::shared_ptr > &outputs) { kleo_assert(d->encrypt); kleo_assert(!d->resolvingInProgress); kleo_assert(!inputs.empty()); kleo_assert(outputs.size() == inputs.size()); std::vector< std::shared_ptr > 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 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 coll(new TaskCollection); std::vector > 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 - for (const std::shared_ptr &t : qAsConst(tmp)) { + for (const std::shared_ptr &t : std::as_const(tmp)) { q->connectTask(t); } schedule(); } void NewSignEncryptEMailController::startSigning(const std::vector< std::shared_ptr > &inputs, const std::vector< std::shared_ptr > &outputs) { kleo_assert(d->sign); kleo_assert(!d->resolvingInProgress); kleo_assert(!inputs.empty()); kleo_assert(!outputs.empty()); std::vector< std::shared_ptr > 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 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 coll(new TaskCollection); std::vector > 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 - for (const std::shared_ptr &t : qAsConst(tmp)) { + for (const std::shared_ptr &t : std::as_const(tmp)) { q->connectTask(t); } schedule(); } void NewSignEncryptEMailController::Private::schedule() { if (!cms) if (const std::shared_ptr t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (cms || openpgp) { return; } kleo_assert(runnable.empty()); q->emitDoneOrError(); } std::shared_ptr NewSignEncryptEMailController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr(); } const std::shared_ptr result = *it; runnable.erase(it); return result; } void NewSignEncryptEMailController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_ASSERT(task); if (result && result->hasError()) { QPointer 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 840bb0815..f02994965 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 #include "signemailcontroller.h" #include "kleopatra_debug.h" #include "signemailtask.h" #include "certificateresolver.h" #include "taskcollection.h" #include #include #include #include #include "emailoperationspreferences.h" #include #include #include #include #include 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 takeRunnable(GpgME::Protocol proto); // ### extract to base private: const Mode mode; std::vector< std::shared_ptr > runnable, completed; // ### extract to base std::shared_ptr cms, openpgp; // ### extract to base QPointer 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 &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()); } void SignEMailController::startResolveSigners(const std::vector &signers) { const std::vector< std::vector > keys = CertificateResolver::resolveSigners(signers, d->protocol); if (!signers.empty()) { kleo_assert(keys.size() == static_cast(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, const std::shared_ptr &output) { setInputsAndOutputs(std::vector< std::shared_ptr >(1, input), std::vector< std::shared_ptr >(1, output)); } // ### extract to base void SignEMailController::setInputsAndOutputs(const std::vector< std::shared_ptr > &inputs, const std::vector< std::shared_ptr > &outputs) { kleo_assert(!inputs.empty()); kleo_assert(!outputs.empty()); std::vector< std::shared_ptr > tasks; tasks.reserve(inputs.size()); d->ensureWizardCreated(); const std::vector keys = d->wizard->resolvedSigners(); kleo_assert(!keys.empty()); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr 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 coll(new TaskCollection); std::vector > tmp; std::copy(d->runnable.begin(), d->runnable.end(), std::back_inserter(tmp)); coll->setTasks(tmp); d->ensureWizardCreated(); d->wizard->setTaskCollection(coll); - for (const std::shared_ptr &t : qAsConst(tmp)) { + for (const std::shared_ptr &t : std::as_const(tmp)) { connectTask(t); } d->schedule(); } // ### extract to base void SignEMailController::Private::schedule() { if (!cms) if (const std::shared_ptr t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (!cms && !openpgp) { kleo_assert(runnable.empty()); QPointer Q = q; Q_FOREACH (const std::shared_ptr t, completed) { Q_EMIT q->reportMicAlg(t->micAlg()); if (!Q) { return; } } q->emitDoneOrError(); } } // ### extract to base std::shared_ptr SignEMailController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr(); } const std::shared_ptr result = *it; runnable.erase(it); return result; } // ### extract to base void SignEMailController::doTaskDone(const Task *task, const std::shared_ptr &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 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/signencryptfilescontroller.cpp b/src/crypto/signencryptfilescontroller.cpp index a93a1a1e4..c5c345796 100644 --- a/src/crypto/signencryptfilescontroller.cpp +++ b/src/crypto/signencryptfilescontroller.cpp @@ -1,703 +1,703 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencryptfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptfilescontroller.h" #include "signencrypttask.h" #include "certificateresolver.h" #include "crypto/gui/signencryptfileswizard.h" #include "crypto/taskcollection.h" #include "fileoperationspreferences.h" #include "utils/input.h" #include "utils/output.h" #include "utils/kleo_assert.h" #include "utils/archivedefinition.h" #include "utils/path-helper.h" #include #include #include #include "kleopatra_debug.h" #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; using namespace KMime::Types; class SignEncryptFilesController::Private { friend class ::Kleo::Crypto::SignEncryptFilesController; SignEncryptFilesController *const q; public: explicit Private(SignEncryptFilesController *qq); ~Private(); private: void slotWizardOperationPrepared(); void slotWizardCanceled(); private: void ensureWizardCreated(); void ensureWizardVisible(); void updateWizardMode(); void cancelAllTasks(); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void schedule(); std::shared_ptr takeRunnable(GpgME::Protocol proto); static void assertValidOperation(unsigned int); static QString titleForOperation(unsigned int op); private: std::vector< std::shared_ptr > runnable, completed; std::shared_ptr cms, openpgp; QPointer wizard; QStringList files; unsigned int operation; Protocol protocol; }; SignEncryptFilesController::Private::Private(SignEncryptFilesController *qq) : q(qq), runnable(), cms(), openpgp(), wizard(), files(), operation(SignAllowed | EncryptAllowed | ArchiveAllowed), protocol(UnknownProtocol) { } SignEncryptFilesController::Private::~Private() { qCDebug(KLEOPATRA_LOG); } QString SignEncryptFilesController::Private::titleForOperation(unsigned int op) { const bool signDisallowed = (op & SignMask) == SignDisallowed; const bool encryptDisallowed = (op & EncryptMask) == EncryptDisallowed; const bool archiveSelected = (op & ArchiveMask) == ArchiveForced; kleo_assert(!signDisallowed || !encryptDisallowed); if (!signDisallowed && encryptDisallowed) { if (archiveSelected) { return i18n("Archive and Sign Files"); } else { return i18n("Sign Files"); } } if (signDisallowed && !encryptDisallowed) { if (archiveSelected) { return i18n("Archive and Encrypt Files"); } else { return i18n("Encrypt Files"); } } if (archiveSelected) { return i18n("Archive and Sign/Encrypt Files"); } else { return i18n("Sign/Encrypt Files"); } } SignEncryptFilesController::SignEncryptFilesController(QObject *p) : Controller(p), d(new Private(this)) { } SignEncryptFilesController::SignEncryptFilesController(const std::shared_ptr &ctx, QObject *p) : Controller(ctx, p), d(new Private(this)) { } SignEncryptFilesController::~SignEncryptFilesController() { qCDebug(KLEOPATRA_LOG); if (d->wizard && !d->wizard->isVisible()) { delete d->wizard; } //d->wizard->close(); ### ? } void SignEncryptFilesController::setProtocol(Protocol proto) { kleo_assert(d->protocol == UnknownProtocol || d->protocol == proto); d->protocol = proto; d->ensureWizardCreated(); } Protocol SignEncryptFilesController::protocol() const { return d->protocol; } // static void SignEncryptFilesController::Private::assertValidOperation(unsigned int op) { kleo_assert((op & SignMask) == SignDisallowed || (op & SignMask) == SignAllowed || (op & SignMask) == SignSelected); kleo_assert((op & EncryptMask) == EncryptDisallowed || (op & EncryptMask) == EncryptAllowed || (op & EncryptMask) == EncryptSelected); kleo_assert((op & ArchiveMask) == ArchiveDisallowed || (op & ArchiveMask) == ArchiveAllowed || (op & ArchiveMask) == ArchiveForced); kleo_assert((op & ~(SignMask | EncryptMask | ArchiveMask)) == 0); } void SignEncryptFilesController::setOperationMode(unsigned int mode) { Private::assertValidOperation(mode); d->operation = mode; d->updateWizardMode(); } void SignEncryptFilesController::Private::updateWizardMode() { if (!wizard) { return; } wizard->setWindowTitle(titleForOperation(operation)); const unsigned int signOp = (operation & SignMask); const unsigned int encrOp = (operation & EncryptMask); const unsigned int archOp = (operation & ArchiveMask); if (signOp == SignDisallowed) { wizard->setSigningUserMutable(false); wizard->setSigningPreset(false); } else { wizard->setSigningUserMutable(true); wizard->setSigningPreset(signOp == SignSelected); } if (encrOp == EncryptDisallowed) { wizard->setEncryptionPreset(false); wizard->setEncryptionUserMutable(false); } else { wizard->setEncryptionUserMutable(true); wizard->setEncryptionPreset(false); wizard->setEncryptionPreset(encrOp == EncryptSelected); } wizard->setArchiveForced(archOp == ArchiveForced); wizard->setArchiveMutable(archOp == ArchiveAllowed); } unsigned int SignEncryptFilesController::operationMode() const { return d->operation; } static const char *extension(bool pgp, bool sign, bool encrypt, bool ascii, bool detached) { unsigned int cls = pgp ? Class::OpenPGP : Class::CMS; if (encrypt) { cls |= Class::CipherText; } else if (sign) { cls |= detached ? Class::DetachedSignature : Class::OpaqueSignature; } cls |= ascii ? Class::Ascii : Class::Binary; const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt(); if (const char *const ext = outputFileExtension(cls, usePGPFileExt)) { return ext; } else { return "out"; } } static std::shared_ptr getDefaultAd() { std::vector > ads = ArchiveDefinition::getArchiveDefinitions(); Q_ASSERT(!ads.empty()); std::shared_ptr ad = ads.front(); const FileOperationsPreferences prefs; Q_FOREACH (const std::shared_ptr toCheck, ads) { if (toCheck->id() == prefs.archiveCommand()) { ad = toCheck; break; } } return ad; } static QMap buildOutputNames(const QStringList &files, const bool archive) { QMap nameMap; // Build the default names for the wizard. QString baseNameCms; QString baseNamePgp; const QFileInfo firstFile(files.first()); if (archive) { QString baseName; baseName = QDir(heuristicBaseDirectory(files)).absoluteFilePath(files.size() > 1 ? i18nc("base name of an archive file, e.g. archive.zip or archive.tar.gz", "archive") : firstFile.baseName()); const auto ad = getDefaultAd(); baseNamePgp = baseName + QLatin1Char('.') + ad->extensions(GpgME::OpenPGP).first() + QLatin1Char('.'); baseNameCms = baseName + QLatin1Char('.') + ad->extensions(GpgME::CMS).first() + QLatin1Char('.'); } else { baseNameCms = baseNamePgp = files.first() + QLatin1Char('.'); } const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); nameMap.insert(SignEncryptFilesWizard::SignatureCMS, baseNameCms + QString::fromLatin1(extension(false, true, false, ascii, true))); nameMap.insert(SignEncryptFilesWizard::EncryptedCMS, baseNameCms + QString::fromLatin1(extension(false, false, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::CombinedPGP, baseNamePgp + QString::fromLatin1(extension(true, true, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::EncryptedPGP, baseNamePgp + QString::fromLatin1(extension(true, false, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::SignaturePGP, baseNamePgp + QString::fromLatin1(extension(true, true, false, ascii, true))); nameMap.insert(SignEncryptFilesWizard::Directory, heuristicBaseDirectory(files)); return nameMap; } static QMap buildOutputNamesForDir(const QString &file, const QMap &orig) { QMap ret; const QString dir = orig.value(SignEncryptFilesWizard::Directory); if (dir.isEmpty()) { return orig; } // Build the default names for the wizard. const QFileInfo fi(file); const QString baseName = dir + QLatin1Char('/') + fi.fileName() + QLatin1Char('.'); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); ret.insert(SignEncryptFilesWizard::SignatureCMS, baseName + QString::fromLatin1(extension(false, true, false, ascii, true))); ret.insert(SignEncryptFilesWizard::EncryptedCMS, baseName + QString::fromLatin1(extension(false, false, true, ascii, false))); ret.insert(SignEncryptFilesWizard::CombinedPGP, baseName + QString::fromLatin1(extension(true, true, true, ascii, false))); ret.insert(SignEncryptFilesWizard::EncryptedPGP, baseName + QString::fromLatin1(extension(true, false, true, ascii, false))); ret.insert(SignEncryptFilesWizard::SignaturePGP, baseName + QString::fromLatin1(extension(true, true, false, ascii, true))); return ret; } void SignEncryptFilesController::setFiles(const QStringList &files) { kleo_assert(!files.empty()); d->files = files; bool archive = false; if (files.size() > 1) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveAllowed); archive = true; } for (const auto &file: files) { if (QFileInfo(file).isDir()) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveForced); archive = true; break; } } d->ensureWizardCreated(); d->wizard->setOutputNames(buildOutputNames(files, archive)); } void SignEncryptFilesController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG); reportError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel")); } void SignEncryptFilesController::start() { d->ensureWizardVisible(); } static std::shared_ptr createSignEncryptTaskForFileInfo(const QFileInfo &fi, bool ascii, const std::vector &recipients, const std::vector &signers, const QString &outputName, bool symmetric) { const std::shared_ptr task(new SignEncryptTask); Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric); task->setAsciiArmor(ascii); if (!signers.empty()) { task->setSign(true); task->setSigners(signers); task->setDetachedSignature(true); } else { task->setSign(false); } if (!recipients.empty()) { task->setEncrypt(true); task->setRecipients(recipients); task->setDetachedSignature(false); } else { task->setEncrypt(false); } task->setEncryptSymmetric(symmetric); const QString input = fi.absoluteFilePath(); task->setInputFileName(input); task->setInput(Input::createFromFile(input)); task->setOutputFileName(outputName); return task; } static std::shared_ptr createArchiveSignEncryptTaskForFiles(const QStringList &files, const std::shared_ptr &ad, bool pgp, bool ascii, const std::vector &recipients, const std::vector &signers, const QString& outputName, bool symmetric) { const std::shared_ptr task(new SignEncryptTask); task->setEncryptSymmetric(symmetric); Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric); task->setAsciiArmor(ascii); if (!signers.empty()) { task->setSign(true); task->setSigners(signers); task->setDetachedSignature(false); } else { task->setSign(false); } if (!recipients.empty()) { task->setEncrypt(true); task->setRecipients(recipients); } else { task->setEncrypt(false); } kleo_assert(ad); const Protocol proto = pgp ? OpenPGP : CMS; task->setInputFileNames(files); task->setInput(ad->createInputFromPackCommand(proto, files)); task->setOutputFileName(outputName); return task; } static std::vector< std::shared_ptr > createSignEncryptTasksForFileInfo(const QFileInfo &fi, bool ascii, const std::vector &pgpRecipients, const std::vector &pgpSigners, const std::vector &cmsRecipients, const std::vector &cmsSigners, const QMap &outputNames, bool symmetric) { std::vector< std::shared_ptr > result; const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty(); const bool cms = !cmsSigners.empty() || !cmsRecipients.empty(); result.reserve(pgp + cms); if (pgp || symmetric) { // Symmetric encryption is only supported for PGP int outKind = 0; if ((!pgpRecipients.empty() || symmetric)&& !pgpSigners.empty()) { outKind = SignEncryptFilesWizard::CombinedPGP; } else if (!pgpRecipients.empty() || symmetric) { outKind = SignEncryptFilesWizard::EncryptedPGP; } else { outKind = SignEncryptFilesWizard::SignaturePGP; } result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric)); } if (cms) { // There is no combined sign / encrypt in gpgsm so we create one sign task // and one encrypt task. Which leaves us with the age old dilemma, encrypt // then sign, or sign then encrypt. Ugly. if (!cmsSigners.empty()) { result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, std::vector(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false)); } if (!cmsRecipients.empty()) { result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, cmsRecipients, std::vector(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false)); } } return result; } static std::vector< std::shared_ptr > createArchiveSignEncryptTasksForFiles(const QStringList &files, const std::shared_ptr &ad, bool ascii, const std::vector &pgpRecipients, const std::vector &pgpSigners, const std::vector &cmsRecipients, const std::vector &cmsSigners, const QMap &outputNames, bool symmetric) { std::vector< std::shared_ptr > result; const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty(); const bool cms = !cmsSigners.empty() || !cmsRecipients.empty(); result.reserve(pgp + cms); if (pgp || symmetric) { int outKind = 0; if ((!pgpRecipients.empty() || symmetric) && !pgpSigners.empty()) { outKind = SignEncryptFilesWizard::CombinedPGP; } else if (!pgpRecipients.empty() || symmetric) { outKind = SignEncryptFilesWizard::EncryptedPGP; } else { outKind = SignEncryptFilesWizard::SignaturePGP; } result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, true, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric)); } if (cms) { if (!cmsSigners.empty()) { result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii, std::vector(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false)); } if (!cmsRecipients.empty()) { result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii, cmsRecipients, std::vector(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false)); } } return result; } void SignEncryptFilesController::Private::slotWizardOperationPrepared() { try { kleo_assert(wizard); kleo_assert(!files.empty()); const bool archive = (wizard->outputNames().value(SignEncryptFilesWizard::Directory).isNull() && files.size() > 1) || ((operation & ArchiveMask) == ArchiveForced); const std::vector recipients = wizard->resolvedRecipients(); const std::vector signers = wizard->resolvedSigners(); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); std::vector pgpRecipients, cmsRecipients, pgpSigners, cmsSigners; Q_FOREACH (const Key &k, recipients) { if (k.protocol() == GpgME::OpenPGP) { pgpRecipients.push_back(k); } else { cmsRecipients.push_back(k); } } Q_FOREACH (const Key &k, signers) { if (k.protocol() == GpgME::OpenPGP) { pgpSigners.push_back(k); } else { cmsSigners.push_back(k); } } std::vector< std::shared_ptr > tasks; if (!archive) { tasks.reserve(files.size()); } if (archive) { tasks = createArchiveSignEncryptTasksForFiles(files, getDefaultAd(), ascii, pgpRecipients, pgpSigners, cmsRecipients, cmsSigners, wizard->outputNames(), wizard->encryptSymmetric()); } else { Q_FOREACH (const QString &file, files) { const std::vector< std::shared_ptr > created = createSignEncryptTasksForFileInfo(QFileInfo(file), ascii, pgpRecipients, pgpSigners, cmsRecipients, cmsSigners, buildOutputNamesForDir(file, wizard->outputNames()), wizard->encryptSymmetric()); tasks.insert(tasks.end(), created.begin(), created.end()); } } const std::shared_ptr overwritePolicy(new OverwritePolicy(wizard)); Q_FOREACH (const std::shared_ptr &i, tasks) { i->setOverwritePolicy(overwritePolicy); } kleo_assert(runnable.empty()); runnable.swap(tasks); - for (const auto &task : qAsConst(runnable)) { + for (const auto &task : std::as_const(runnable)) { q->connectTask(task); } std::shared_ptr coll(new TaskCollection); std::vector > tmp; std::copy(runnable.begin(), runnable.end(), std::back_inserter(tmp)); coll->setTasks(tmp); wizard->setTaskCollection(coll); QTimer::singleShot(0, q, SLOT(schedule())); } catch (const Kleo::Exception &e) { reportError(e.error().encodedError(), e.message()); } catch (const std::exception &e) { reportError(gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unexpected exception in SignEncryptFilesController::Private::slotWizardOperationPrepared: %1", QString::fromLocal8Bit(e.what()))); } catch (...) { reportError(gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in SignEncryptFilesController::Private::slotWizardOperationPrepared")); } } void SignEncryptFilesController::Private::schedule() { if (!cms) if (const std::shared_ptr t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (!cms && !openpgp) { kleo_assert(runnable.empty()); q->emitDoneOrError(); } } std::shared_ptr SignEncryptFilesController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr(); } const std::shared_ptr result = *it; runnable.erase(it); return result; } void SignEncryptFilesController::doTaskDone(const Task *task, const std::shared_ptr &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 SignEncryptFilesController::cancel() { qCDebug(KLEOPATRA_LOG); try { if (d->wizard) { d->wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void SignEncryptFilesController::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 SignEncryptFilesController::Private::ensureWizardCreated() { if (wizard) { return; } std::unique_ptr w(new SignEncryptFilesWizard); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection); connect(w.get(), SIGNAL(rejected()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); wizard = w.release(); updateWizardMode(); } void SignEncryptFilesController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(wizard); } #include "moc_signencryptfilescontroller.cpp" diff --git a/src/dialogs/certificateselectiondialog.cpp b/src/dialogs/certificateselectiondialog.cpp index 87d6ec1bc..e87a749e9 100644 --- a/src/dialogs/certificateselectiondialog.cpp +++ b/src/dialogs/certificateselectiondialog.cpp @@ -1,473 +1,473 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/certificateselectiondialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "certificateselectiondialog.h" #include "settings.h" #include "conf/groupsconfigdialog.h" #include #include #include #include "utils/tags.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_REMARKS #endif using namespace Kleo; using namespace Kleo::Dialogs; using namespace Kleo::Commands; using namespace GpgME; CertificateSelectionDialog::Option CertificateSelectionDialog::optionsFromProtocol(Protocol proto) { switch (proto) { case OpenPGP: return CertificateSelectionDialog::OpenPGPFormat; case CMS: return CertificateSelectionDialog::CMSFormat; default: return CertificateSelectionDialog::AnyFormat; } } class CertificateSelectionDialog::Private { friend class ::Kleo::Dialogs::CertificateSelectionDialog; CertificateSelectionDialog *const q; public: explicit Private(CertificateSelectionDialog *qq); private: void reload() { Command *const cmd = new ReloadKeysCommand(nullptr); cmd->setParentWidget(q); cmd->start(); } void create() { auto cmd = new NewCertificateCommand(nullptr); cmd->setParentWidget(q); if ((options & AnyFormat) != AnyFormat) { cmd->setProtocol((options & OpenPGPFormat) ? OpenPGP : CMS); } cmd->start(); } void lookup() { Command *const cmd = new LookupCertificatesCommand(nullptr); cmd->setParentWidget(q); cmd->start(); } void manageGroups() { KConfigDialog *dialog = KConfigDialog::exists(GroupsConfigDialog::dialogName()); if (dialog) { // reparent the dialog to ensure it's shown on top of the modal CertificateSelectionDialog dialog->setParent(q, Qt::Dialog); } else { dialog = new GroupsConfigDialog(q); } dialog->show(); } void slotKeysMayHaveChanged(); void slotCurrentViewChanged(QAbstractItemView *newView); void slotSelectionChanged(); void slotDoubleClicked(const QModelIndex &idx); private: bool acceptable(const std::vector &keys, const std::vector &groups) { return !keys.empty() || !groups.empty(); } void updateLabelText() { ui.label.setText(!customLabelText.isEmpty() ? customLabelText : (options & MultiSelection) ? i18n("Please select one or more of the following certificates:") : i18n("Please select one of the following certificates:")); } private: QSet connectedViews; QString customLabelText; Options options = AnyCertificate | AnyFormat; struct UI { QLabel label; SearchBar searchBar; TabWidget tabWidget; QDialogButtonBox buttonBox; } ui; void setUpUI(CertificateSelectionDialog *q) { KDAB_SET_OBJECT_NAME(ui.label); KDAB_SET_OBJECT_NAME(ui.searchBar); KDAB_SET_OBJECT_NAME(ui.tabWidget); KDAB_SET_OBJECT_NAME(ui.buttonBox); auto vlay = new QVBoxLayout(q); vlay->addWidget(&ui.label); vlay->addWidget(&ui.searchBar); vlay->addWidget(&ui.tabWidget, 1); vlay->addWidget(&ui.buttonBox); QPushButton *const okButton = ui.buttonBox.addButton(QDialogButtonBox::Ok); okButton->setEnabled(false); ui.buttonBox.addButton(QDialogButtonBox::Close); QPushButton *const reloadButton = ui.buttonBox.addButton(i18n("Reload"), QDialogButtonBox::ActionRole); QPushButton *const importButton = ui.buttonBox.addButton(i18n("Import..."), QDialogButtonBox::ActionRole); QPushButton *const lookupButton = ui.buttonBox.addButton(i18n("Lookup..."), QDialogButtonBox::ActionRole); QPushButton *const createButton = ui.buttonBox.addButton(i18n("New..."), QDialogButtonBox::ActionRole); QPushButton *const groupsButton = ui.buttonBox.addButton(i18n("Groups..."), QDialogButtonBox::ActionRole); groupsButton->setVisible(Settings().groupsEnabled()); importButton->setToolTip(i18nc("@info:tooltip", "Import certificate from file")); lookupButton->setToolTip(i18nc("@info:tooltip", "Lookup certificates on server")); reloadButton->setToolTip(i18nc("@info:tooltip", "Refresh certificate list")); createButton->setToolTip(i18nc("@info:tooltip", "Create a new certificate")); groupsButton->setToolTip(i18nc("@info:tooltip", "Manage certificate groups")); connect(&ui.buttonBox, &QDialogButtonBox::accepted, q, &CertificateSelectionDialog::accept); connect(&ui.buttonBox, &QDialogButtonBox::rejected, q, &CertificateSelectionDialog::reject); connect(reloadButton, &QPushButton::clicked, q, [this] () { reload(); }); connect(lookupButton, &QPushButton::clicked, q, [this] () { lookup(); }); connect(createButton, &QPushButton::clicked, q, [this] () { create(); }); connect(groupsButton, &QPushButton::clicked, q, [this] () { manageGroups(); }); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, q, [this] () { slotKeysMayHaveChanged(); }); connect(importButton, &QPushButton::clicked, q, [importButton, q] () { importButton->setEnabled(false); auto cmd = new Kleo::ImportCertificateFromFileCommand(); connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [importButton]() { importButton->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } }; CertificateSelectionDialog::Private::Private(CertificateSelectionDialog *qq) : q(qq) { setUpUI(q); ui.tabWidget.setFlatModel(AbstractKeyListModel::createFlatKeyListModel(q)); ui.tabWidget.setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(q)); #ifdef GPGME_HAS_REMARKS const auto tagKeys = Tags::tagKeys(); ui.tabWidget.flatModel()->setRemarkKeys(tagKeys); ui.tabWidget.hierarchicalModel()->setRemarkKeys(tagKeys); #endif ui.tabWidget.connectSearchBar(&ui.searchBar); connect(&ui.tabWidget, &TabWidget::currentViewChanged, q, [this] (QAbstractItemView *view) { slotCurrentViewChanged(view); }); updateLabelText(); q->setWindowTitle(i18nc("@title:window", "Certificate Selection")); } CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent) : QDialog(parent), d(new Private(this)) { const KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kleopatracertificateselectiondialogrc")); d->ui.tabWidget.loadViews(config.data()); const KConfigGroup geometry(config, "Geometry"); resize(geometry.readEntry("size", size())); d->slotKeysMayHaveChanged(); } CertificateSelectionDialog::~CertificateSelectionDialog() {} void CertificateSelectionDialog::setCustomLabelText(const QString &txt) { if (txt == d->customLabelText) { return; } d->customLabelText = txt; d->updateLabelText(); } QString CertificateSelectionDialog::customLabelText() const { return d->customLabelText; } void CertificateSelectionDialog::setOptions(Options options) { Q_ASSERT((options & CertificateSelectionDialog::AnyCertificate) != 0); Q_ASSERT((options & CertificateSelectionDialog::AnyFormat) != 0); if (d->options == options) { return; } d->options = options; d->ui.tabWidget.setMultiSelection(options & MultiSelection); d->slotKeysMayHaveChanged(); } CertificateSelectionDialog::Options CertificateSelectionDialog::options() const { return d->options; } void CertificateSelectionDialog::setStringFilter(const QString &filter) { d->ui.tabWidget.setStringFilter(filter); } void CertificateSelectionDialog::setKeyFilter(const std::shared_ptr &filter) { d->ui.tabWidget.setKeyFilter(filter); } namespace { void selectRows(const QAbstractItemView *view, const QModelIndexList &indexes) { if (!view) { return; } QItemSelectionModel *const sm = view->selectionModel(); Q_ASSERT(sm); - for (const QModelIndex &idx : qAsConst(indexes)) { + for (const QModelIndex &idx : std::as_const(indexes)) { if (idx.isValid()) { sm->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); } } } QModelIndexList getGroupIndexes(const KeyListModelInterface *model, const std::vector &groups) { QModelIndexList indexes; indexes.reserve(groups.size()); std::transform(groups.begin(), groups.end(), std::back_inserter(indexes), [model] (const KeyGroup &group) { return model->index(group); }); indexes.erase(std::remove_if(indexes.begin(), indexes.end(), [] (const QModelIndex &index) { return !index.isValid(); }), indexes.end()); return indexes; } } void CertificateSelectionDialog::selectCertificates(const std::vector &keys) { const auto *const model = d->ui.tabWidget.currentModel(); Q_ASSERT(model); selectRows(d->ui.tabWidget.currentView(), model->indexes(keys)); } void CertificateSelectionDialog::selectCertificate(const Key &key) { selectCertificates(std::vector(1, key)); } void CertificateSelectionDialog::selectGroups(const std::vector &groups) { const auto *const model = d->ui.tabWidget.currentModel(); Q_ASSERT(model); selectRows(d->ui.tabWidget.currentView(), getGroupIndexes(model, groups)); } namespace { QModelIndexList getSelectedRows(const QAbstractItemView *view) { if (!view) { return {}; } const QItemSelectionModel *const sm = view->selectionModel(); Q_ASSERT(sm); return sm->selectedRows(); } std::vector getGroups(const KeyListModelInterface *model, const QModelIndexList &indexes) { std::vector groups; groups.reserve(indexes.size()); std::transform(indexes.begin(), indexes.end(), std::back_inserter(groups), [model](const QModelIndex &idx) { return model->group(idx); }); groups.erase(std::remove_if(groups.begin(), groups.end(), std::mem_fn(&Kleo::KeyGroup::isNull)), groups.end()); return groups; } } std::vector CertificateSelectionDialog::selectedCertificates() const { const KeyListModelInterface *const model = d->ui.tabWidget.currentModel(); Q_ASSERT(model); return model->keys(getSelectedRows(d->ui.tabWidget.currentView())); } Key CertificateSelectionDialog::selectedCertificate() const { const std::vector keys = selectedCertificates(); return keys.empty() ? Key() : keys.front(); } std::vector CertificateSelectionDialog::selectedGroups() const { const KeyListModelInterface *const model = d->ui.tabWidget.currentModel(); Q_ASSERT(model); return getGroups(model, getSelectedRows(d->ui.tabWidget.currentView())); } void CertificateSelectionDialog::hideEvent(QHideEvent *e) { KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kleopatracertificateselectiondialogrc")); d->ui.tabWidget.saveViews(config.data()); KConfigGroup geometry(config, "Geometry"); geometry.writeEntry("size", size()); QDialog::hideEvent(e); } void CertificateSelectionDialog::Private::slotKeysMayHaveChanged() { q->setEnabled(true); std::vector keys = (options & SecretKeys) ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys(); q->filterAllowedKeys(keys, options); const std::vector groups = (options & IncludeGroups) ? KeyCache::instance()->groups() : std::vector(); const std::vector selectedKeys = q->selectedCertificates(); const std::vector selectedGroups = q->selectedGroups(); if (AbstractKeyListModel *const model = ui.tabWidget.flatModel()) { model->setKeys(keys); model->setGroups(groups); } if (AbstractKeyListModel *const model = ui.tabWidget.hierarchicalModel()) { model->setKeys(keys); model->setGroups(groups); } q->selectCertificates(selectedKeys); q->selectGroups(selectedGroups); } void CertificateSelectionDialog::filterAllowedKeys(std::vector &keys, int options) { auto end = keys.end(); switch (options & AnyFormat) { case OpenPGPFormat: end = std::remove_if(keys.begin(), end, [](const Key &key) { return key.protocol() != OpenPGP; }); break; case CMSFormat: end = std::remove_if(keys.begin(), end, [](const Key &key) { return key.protocol() != CMS; }); break; default: case AnyFormat: ; } switch (options & AnyCertificate) { case SignOnly: end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.canReallySign(); }); break; case EncryptOnly: end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.canEncrypt(); }); break; default: case AnyCertificate: ; } if (options & SecretKeys) { end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.hasSecret(); }); } keys.erase(end, keys.end()); } void CertificateSelectionDialog::Private::slotCurrentViewChanged(QAbstractItemView *newView) { if (!connectedViews.contains(newView)) { connectedViews.insert(newView); connect(newView, &QAbstractItemView::doubleClicked, q, [this] (const QModelIndex &index) { slotDoubleClicked(index); }); Q_ASSERT(newView->selectionModel()); connect(newView->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this] (const QItemSelection &, const QItemSelection &) { slotSelectionChanged(); }); } slotSelectionChanged(); } void CertificateSelectionDialog::Private::slotSelectionChanged() { if (QPushButton *const pb = ui.buttonBox.button(QDialogButtonBox::Ok)) { pb->setEnabled(acceptable(q->selectedCertificates(), q->selectedGroups())); } } void CertificateSelectionDialog::Private::slotDoubleClicked(const QModelIndex &idx) { QAbstractItemView *const view = ui.tabWidget.currentView(); Q_ASSERT(view); const auto *const model = ui.tabWidget.currentModel(); Q_ASSERT(model); Q_UNUSED(model) QItemSelectionModel *const sm = view->selectionModel(); Q_ASSERT(sm); sm->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); QMetaObject::invokeMethod(q, [this]() {q->accept();}, Qt::QueuedConnection); } void CertificateSelectionDialog::accept() { if (d->acceptable(selectedCertificates(), selectedGroups())) { QDialog::accept(); } } #include "moc_certificateselectiondialog.cpp" diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp index 90d7fd0dc..2e62dd7a5 100644 --- a/src/kleopatraapplication.cpp +++ b/src/kleopatraapplication.cpp @@ -1,651 +1,651 @@ /* 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 #include "kleopatraapplication.h" #include "mainwindow.h" #include "kleopatra_options.h" #include "systrayicon.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_USABLE_ASSUAN # include #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 #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; static void add_resources() { KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra")); KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg")); } static QList default_logging_options() { QList 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; QPointer mainWindow; SmartCard::ReaderStatus readerStatus; #ifndef QT_NO_SYSTEMTRAYICON SysTrayIcon *sysTray; #endif std::shared_ptr keyCache; std::shared_ptr log; std::shared_ptr watcher; public: void setupKeyCache() { keyCache = KeyCache::mutableInstance(); watcher.reset(new FileSystemWatcher); watcher->whitelistFiles(gnupgFileWhitelist()); watcher->addPath(gnupgHomeDirectory()); watcher->setDelay(1000); keyCache->addFileSystemWatcher(watcher); keyCache->setGroupsConfig(QStringLiteral("kleopatragroupsrc")); keyCache->setGroupsEnabled(Settings().groupsEnabled()); } void setupLogging() { log = Log::mutableInstance(); const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS"); const bool logAll = envOptions.trimmed() == "all"; const QList 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 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 { using Func = void (KleopatraApplication::*)(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) { 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(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"); } auto 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 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(R"(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 { - for (const QString& fileName : qAsConst(files)) { + for (const QString& fileName : std::as_const(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; } auto 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) { auto 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) { auto 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) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Decrypt); cmd->start(); } void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Verify); cmd->start(); } void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto 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 9ea6f68ec..0c774ab29 100644 --- a/src/newcertificatewizard/newcertificatewizard.cpp +++ b/src/newcertificatewizard/newcertificatewizard.cpp @@ -1,1910 +1,1910 @@ /* -*- 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 #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 &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(algo), RSA); } static bool is_dsa(unsigned int algo) { return is_algo(static_cast(algo), DSA); } static bool is_elg(unsigned int algo) { return is_algo(static_cast(algo), ELG); } static bool is_ecdsa(unsigned int algo) { return is_algo(static_cast(algo), ECDSA); } static bool is_eddsa(unsigned int algo) { return is_algo(static_cast(algo), EDDSA); } static bool is_ecdh(unsigned int algo) { return is_algo(static_cast(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(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(QWizardPage::wizard()) == qobject_cast(QWizardPage::wizard())); return static_cast(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(); } 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"); 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
    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 = getCryptoConfigEntry(conf, "gpg-agent", "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 lineList; QList dynamicWidgets; QMap 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 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... auto 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 %1 " "to file %2: %3", src.fileName(), fileName, src.errorString()), i18nc("@title", "Error Saving Request")); else KMessageBox::information(this, xi18nc("@info", "Successfully wrote request to %1." "You should now send the request to the Certification Authority (CA).", 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; } auto 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", "Kleopatra tried to send a mail via your default mail client." "Some mail clients are known not to support attachments when invoked this way." "If your mail client does not have an attachment, then drag the Kleopatra icon and drop it on the message compose window of your mail client." "If that does not work, either, save the request to a file, and then attach that."), 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; 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)) { + for (const Line &line : std::as_const(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()); auto lb = qobject_cast(l->itemAtPosition(row, 0)->widget()); Q_ASSERT(lb); auto le = qobject_cast(l->itemAtPosition(row, 1)->widget()); Q_ASSERT(le); lb->setBuddy(le); // For better accessibility auto reqLB = qobject_cast(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 *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 widgets; widgets.push_back(ui.nameLE); widgets.push_back(ui.emailLE); QMap lines; - for (const QString &rawKey : qAsConst(attrOrder)) { + for (const QString &rawKey : std::as_const(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::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 &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", "%1 is required, but empty.", line.label); } else error = xi18nc("@info", "%1 is required, but empty." "Local Admin rule: %2", line.label, line.regex); return false; } } else if (has_intermediate_input(le)) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is incomplete.", line.label); } else error = xi18nc("@info", "%1 is incomplete." "Local Admin rule: %2", line.label, line.regex); return false; } else if (!le->hasAcceptableInput()) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is invalid.", line.label); } else error = xi18nc("@info", "%1 is invalid." "Local Admin rule: %2", 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 struct Row { QString key; T value; Row(const QString &k, const T &v) : key(k), value(v) {} }; template QTextStream &operator<<(QTextStream &s, const Row &row) { if (row.key.isEmpty()) { return s; } else { return s << "" << row.key << "" << row.value << ""; } } } 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 &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 rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << 2048 << -3072 << 4096); if (Kleo::gpgComplianceP("de-vs")) { rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << -3072 << 4096); } const QList dsaKeySizes = config.readEntry(DSA_KEYSIZES_ENTRY, QList() << -2048); const QList elgKeySizes = config.readEntry(ELG_KEYSIZES_ENTRY, QList() << -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 = getCryptoConfigEntry(conf, protocol == CMS ? "gpgsm" : "gpg", "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 [/usage]+[/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 51e5d297c..f0e3cb5cc 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,1128 +1,1128 @@ /* -*- 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 SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #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 #include #include #ifdef QGPGME_HAS_DEBUG # include #endif #include #include #include #include #include "openpgpcard.h" #include "netkeycard.h" #include "pivcard.h" #include "p15card.h" #include #include #include #include #include #include #include #include #include #include #include #include #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 > &v) { using pair = std::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 &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 static std::unique_ptr gpgagent_transact(std::shared_ptr &gpgAgent, const char *command, std::unique_ptr 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(); } std::unique_ptr t = gpgAgent->takeLastAssuanTransaction(); return std::unique_ptr(dynamic_cast(t.release())); } static std::unique_ptr gpgagent_default_transact(std::shared_ptr &gpgAgent, const char *command, Error &err) { return gpgagent_transact(gpgAgent, command, std::make_unique(), err); } static const std::string gpgagent_data(std::shared_ptr gpgAgent, const char *command, Error &err) { const std::unique_ptr 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 > gpgagent_statuslines(std::shared_ptr gpgAgent, const char *what, Error &err) { const std::unique_ptr 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 >(); } } static const std::string gpgagent_status(const std::shared_ptr &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 &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 &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 getCardsAndApps(std::shared_ptr &gpgAgent, Error &err) { std::vector 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 &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 &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 bool isOpenPGPCardSerialNumber(const std::string &serialNumber) { return serialNumber.size() == 32 && serialNumber.substr(0, 12) == "D27600012401"; } static const std::string getDisplaySerialNumber(std::shared_ptr &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 &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 &ci, std::shared_ptr &gpg_agent) { Error err; auto pgpCard = new OpenPGPCard(*ci); const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err.code()) { ci->setStatus(Card::CardError); return; } pgpCard->setCardInfo(info); if (pgpCard->manufacturer().empty()) { // fallback in case MANUFACTURER is not yet included in the card info pgpCard->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber())); } setDisplaySerialNumber(pgpCard, gpg_agent); ci.reset(pgpCard); } static void readKeyPairInfoFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr &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 &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 &ci, std::shared_ptr &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_p15_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto p15Card = new P15Card(*ci); auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } const auto fprs = gpgagent_statuslines(gpg_agent, "SCD GETATTR KEY-FPR", err); if (!err) { info.insert(info.end(), fprs.begin(), fprs.end()); } /* Create the key stubs */ gpgagent_statuslines(gpg_agent, "READKEY --card --no-data -- $SIGNKEYID", err); gpgagent_statuslines(gpg_agent, "READKEY --card --no-data -- $ENCRKEYID", err); p15Card->setCardInfo(info); setDisplaySerialNumber(p15Card, gpg_agent); ci.reset(p15Card); } static void handle_netkey_card(std::shared_ptr &ci, std::shared_ptr &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 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 get_card_status(const std::string &serialNumber, const std::string &appName, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << serialNumber << ',' << appName << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr(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"; ci->setAuthenticationKeyRef(OpenPGPCard::pgpAuthKeyRef()); 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 if (appName == P15Card::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found P15 card" << ci->serialNumber().c_str() << "end"; handle_p15_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 > update_cardinfo(std::shared_ptr &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 >(); } else { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; auto ci = std::shared_ptr(new Card()); ci->setStatus(Card::CardError); return std::vector >(1, ci); } } } Error err; const std::vector cardApps = getCardsAndApps(gpgAgent, err); if (err) { if (isCardNotPresentError(err)) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present"; return std::vector >(); } else { qCWarning(KLEOPATRA_LOG) << "Getting active apps on all inserted cards failed:" << err; auto ci = std::shared_ptr(new Card()); ci->setStatus(Card::CardError); return std::vector >(1, ci); } } std::vector > 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 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 > 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 ft; KDAB_SYNCHRONIZED(m_mutex) ft.splice(ft.begin(), m_finishedTransactions); - for (const Transaction &t : qAsConst(ft)) + for (const Transaction &t : std::as_const(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 gpgAgent; CardApp cardApp; QByteArray command; bool nullSlot = false; AssuanTransaction* assuanTransaction = nullptr; std::list item; std::vector > oldCards; Error err; std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr(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 > 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) { 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), 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 > m_cardInfos; std::list 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("Kleo::SmartCard::Card::Status"); qRegisterMetaType("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 &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 &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"); } 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, 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, const QByteArray &command, QObject *receiver, const char *slot, std::unique_ptr 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 > ReaderStatus::getCards() const { return d->cardInfos(); } std::shared_ptr 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(); } // static std::string ReaderStatus::switchCard(std::shared_ptr& ctx, const std::string& serialNumber, Error& err) { return ::switchCard(ctx, serialNumber, err); } // static std::string ReaderStatus::switchApp(std::shared_ptr& 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 c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return err; } auto assuanContext = std::shared_ptr(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 f70f3d5ee..67372688c 100644 --- a/src/view/keytreeview.cpp +++ b/src/view/keytreeview.cpp @@ -1,725 +1,725 @@ /* -*- 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 #include "keytreeview.h" #include #include #include #include #include #include "utils/headerview.h" #include "utils/tags.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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) { auto e = static_cast(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); } auto tv = qobject_cast (parent()); if (tv) { tv->resizeColumns(); } }); } - for (QAction *action : qAsConst(mColumnActions)) { + for (QAction *action : std::as_const(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 mColumnActions; }; const KeyListModelInterface * keyListModel(const QTreeView &view) { const KeyListModelInterface *const klmi = dynamic_cast(view.model()); Q_ASSERT(klmi); return klmi; } } // 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 &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 &sizes) { if (sizes.empty()) { return; } Q_ASSERT(m_view); Q_ASSERT(m_view->header()); Q_ASSERT(qobject_cast(m_view->header()) == static_cast(m_view->header())); if (auto const hv = static_cast(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 KeyTreeView::columnSizes() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); Q_ASSERT(qobject_cast(m_view->header()) == static_cast(m_view->header())); if (auto const hv = static_cast(m_view->header())) { return hv->sectionSizes(); } else { return std::vector(); } } void KeyTreeView::init() { KDAB_SET_OBJECT_NAME(m_proxy); KDAB_SET_OBJECT_NAME(m_view); if (m_group.isValid()) { // 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); auto 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); auto rearangingModel = new KeyRearrangeColumnsProxyModel(this); rearangingModel->setSourceModel(m_proxy); rearangingModel->setSourceColumns(QVector() << KeyList::PrettyName << KeyList::PrettyEMail << KeyList::Validity << KeyList::ValidFrom << KeyList::ValidUntil << KeyList::TechnicalDetails << KeyList::KeyID << KeyList::Fingerprint << KeyList::OwnerTrust << KeyList::Origin << KeyList::LastUpdate << KeyList::Issuer << KeyList::SerialNumber #ifdef GPGME_HAS_REMARKS // If a column is added before this TAGS_COLUMN define has to be updated accordingly << KeyList::Remarks #endif ); m_view->setModel(rearangingModel); /* Handle expansion state */ if (m_group.isValid()) { 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(KeyList::KeyRole).value(); if (key.isNull()) { return; } const auto fpr = QString::fromLatin1(key.primaryFingerprint()); if (m_expandedKeys.contains(fpr)) { return; } m_expandedKeys << fpr; if (m_group.isValid()) { 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(KeyList::KeyRole).value(); if (key.isNull()) { return; } m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint())); if (m_group.isValid()) { 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(); if (m_group.isValid()) { restoreLayout(m_group); } } 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)) { + for (const auto &fpr: std::as_const(m_expandedKeys)) { const KeyListModelInterface *const km = keyListModel(*m_view); 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(KConfigGroup &group) { 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)); } group.writeEntry("ColumnVisibility", columnVisibility); group.writeEntry("ColumnOrder", columnOrder); group.writeEntry("ColumnWidths", columnWidths); group.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); if (header->isSortIndicatorShown()) { group.writeEntry("SortColumn", header->sortIndicatorSection()); } else { group.writeEntry("SortColumn", -1); } } void KeyTreeView::restoreLayout(const KConfigGroup &group) { QHeaderView *header = m_view->header(); QVariantList columnVisibility = group.readEntry("ColumnVisibility", QVariantList()); QVariantList columnOrder = group.readEntry("ColumnOrder", QVariantList()); QVariantList columnWidths = 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 = group.readEntry("SortAscending", (int)Qt::AscendingOrder); int sortColumn = group.readEntry("SortColumn", 0); if (sortColumn >= 0) { m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); } } KeyTreeView::~KeyTreeView() { if (m_group.isValid()) { saveLayout(m_group); } } static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm) { Q_ASSERT(pm); while (auto const sm = qobject_cast(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 &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); } namespace { QItemSelection itemSelectionFromKeys(const std::vector &keys, const QTreeView &view) { const QModelIndexList indexes = keyListModel(view)->indexes(keys); return std::accumulate( indexes.cbegin(), indexes.cend(), QItemSelection(), [] (QItemSelection &selection, const QModelIndex &index) { if (index.isValid()) { selection.merge(QItemSelection(index, index), QItemSelectionModel::Select); } return selection; }); } } void KeyTreeView::selectKeys(const std::vector &keys) { m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_view), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } std::vector KeyTreeView::selectedKeys() const { return keyListModel(*m_view)->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 selectedKeys = this->selectedKeys(); const Key currentKey = keyListModel(*m_view)->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 = keyListModel(*m_view)->index(currentKey); if (currentIndex.isValid()) { m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate); m_view->scrollTo(currentIndex); } } Q_EMIT hierarchicalChanged(on); } void KeyTreeView::setKeys(const std::vector &keys) { std::vector 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 &keys, bool select) { if (keys.empty()) { return; } if (m_keys.empty()) { setKeys(keys); return; } std::vector sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); std::vector 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 &keys) { addKeysImpl(keys, true); } void KeyTreeView::addKeysUnselected(const std::vector &keys) { addKeysImpl(keys, false); } void KeyTreeView::removeKeys(const std::vector &keys) { if (keys.empty()) { return; } std::vector sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); std::vector 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()); 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)), SLOT(setKeyFilter(std::shared_ptr)) }, }; 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(KeyList::PrettyName, 260); m_view->setColumnWidth(KeyList::PrettyEMail, 260); for (int i = 2; i < m_view->model()->columnCount(); ++i) { m_view->resizeColumnToContents(i); } } diff --git a/src/view/tabwidget.cpp b/src/view/tabwidget.cpp index 13c3faf8d..699beea32 100644 --- a/src/view/tabwidget.cpp +++ b/src/view/tabwidget.cpp @@ -1,959 +1,959 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/tabwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "tabwidget.h" #include "keytreeview.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; namespace { class Page : public Kleo::KeyTreeView { Q_OBJECT Page(const Page &other); public: Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &toolTip = QString(), QWidget *parent = nullptr, const KConfigGroup &group = KConfigGroup()); Page(const KConfigGroup &group, QWidget *parent = nullptr); ~Page(); void setTemporary(bool temporary); bool isTemporary() const { return m_isTemporary; } void setHierarchicalView(bool hierarchical) override; void setStringFilter(const QString &filter) override; void setKeyFilter(const std::shared_ptr &filter) override; QString title() const { return m_title.isEmpty() && keyFilter() ? keyFilter()->name() : m_title; } void setTitle(const QString &title); QString toolTip() const { return m_toolTip.isEmpty() ? title() : m_toolTip; } // not used void setToolTip(const QString &tip); bool canBeClosed() const { return m_canBeClosed; } bool canBeRenamed() const { return m_canBeRenamed; } bool canChangeStringFilter() const { return m_canChangeStringFilter; } bool canChangeKeyFilter() const { return m_canChangeKeyFilter && !m_isTemporary; } bool canChangeHierarchical() const { return m_canChangeHierarchical; } void saveTo(KConfigGroup &group) const; Page *clone() const override { return new Page(*this); } void liftAllRestrictions() { m_canBeClosed = m_canBeRenamed = m_canChangeStringFilter = m_canChangeKeyFilter = m_canChangeHierarchical = true; } Q_SIGNALS: void titleChanged(const QString &title); private: void init(); private: QString m_title; QString m_toolTip; bool m_isTemporary : 1; bool m_canBeClosed : 1; bool m_canBeRenamed : 1; bool m_canChangeStringFilter : 1; bool m_canChangeKeyFilter : 1; bool m_canChangeHierarchical : 1; }; } // anon namespace Page::Page(const Page &other) : KeyTreeView(other), m_title(other.m_title), m_toolTip(other.m_toolTip), m_isTemporary(other.m_isTemporary), m_canBeClosed(other.m_canBeClosed), m_canBeRenamed(other.m_canBeRenamed), m_canChangeStringFilter(other.m_canChangeStringFilter), m_canChangeKeyFilter(other.m_canChangeKeyFilter), m_canChangeHierarchical(other.m_canChangeHierarchical) { init(); } Page::Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy, const QString &toolTip, QWidget *parent, const KConfigGroup &group) : KeyTreeView(text, KeyFilterManager::instance()->keyFilterByID(id), proxy, parent, group), m_title(title), m_toolTip(toolTip), m_isTemporary(false), m_canBeClosed(true), m_canBeRenamed(true), m_canChangeStringFilter(true), m_canChangeKeyFilter(true), m_canChangeHierarchical(true) { init(); } static const char TITLE_ENTRY[] = "title"; static const char STRING_FILTER_ENTRY[] = "string-filter"; static const char KEY_FILTER_ENTRY[] = "key-filter"; static const char HIERARCHICAL_VIEW_ENTRY[] = "hierarchical-view"; static const char COLUMN_SIZES[] = "column-sizes"; static const char SORT_COLUMN[] = "sort-column"; static const char SORT_DESCENDING[] = "sort-descending"; Page::Page(const KConfigGroup &group, QWidget *parent) : KeyTreeView(group.readEntry(STRING_FILTER_ENTRY), KeyFilterManager::instance()->keyFilterByID(group.readEntry(KEY_FILTER_ENTRY)), nullptr, parent, group), m_title(group.readEntry(TITLE_ENTRY)), m_toolTip(), m_isTemporary(false), m_canBeClosed(!group.isImmutable()), m_canBeRenamed(!group.isEntryImmutable(TITLE_ENTRY)), m_canChangeStringFilter(!group.isEntryImmutable(STRING_FILTER_ENTRY)), m_canChangeKeyFilter(!group.isEntryImmutable(KEY_FILTER_ENTRY)), m_canChangeHierarchical(!group.isEntryImmutable(HIERARCHICAL_VIEW_ENTRY)) { init(); setHierarchicalView(group.readEntry(HIERARCHICAL_VIEW_ENTRY, true)); const QList settings = group.readEntry(COLUMN_SIZES, QList()); std::vector sizes; sizes.reserve(settings.size()); std::copy(settings.cbegin(), settings.cend(), std::back_inserter(sizes)); setColumnSizes(sizes); setSortColumn(group.readEntry(SORT_COLUMN, 0), group.readEntry(SORT_DESCENDING, true) ? Qt::DescendingOrder : Qt::AscendingOrder); } void Page::init() { } Page::~Page() {} void Page::saveTo(KConfigGroup &group) const { group.writeEntry(TITLE_ENTRY, m_title); group.writeEntry(STRING_FILTER_ENTRY, stringFilter()); group.writeEntry(KEY_FILTER_ENTRY, keyFilter() ? keyFilter()->id() : QString()); group.writeEntry(HIERARCHICAL_VIEW_ENTRY, isHierarchicalView()); QList settings; const auto sizes = columnSizes(); settings.reserve(sizes.size()); std::copy(sizes.cbegin(), sizes.cend(), std::back_inserter(settings)); group.writeEntry(COLUMN_SIZES, settings); group.writeEntry(SORT_COLUMN, sortColumn()); group.writeEntry(SORT_DESCENDING, sortOrder() == Qt::DescendingOrder); } void Page::setStringFilter(const QString &filter) { if (!m_canChangeStringFilter) { return; } KeyTreeView::setStringFilter(filter); } void Page::setKeyFilter(const std::shared_ptr &filter) { if (!canChangeKeyFilter()) { return; } const QString oldTitle = title(); KeyTreeView::setKeyFilter(filter); const QString newTitle = title(); if (oldTitle != newTitle) { Q_EMIT titleChanged(newTitle); } } void Page::setTitle(const QString &t) { if (t == m_title) { return; } if (!m_canBeRenamed) { return; } const QString oldTitle = title(); m_title = t; const QString newTitle = title(); if (oldTitle != newTitle) { Q_EMIT titleChanged(newTitle); } } #if 0 // not used void Page::setToolTip(const QString &tip) { if (tip == m_toolTip) { return; } if (!m_canBeRenamed) { return; } const QString oldTip = toolTip(); m_toolTip = tip; const QString newTip = toolTip(); if (oldTip != newTip) { Q_EMIT titleChanged(title()); } } #endif void Page::setHierarchicalView(bool on) { if (!m_canChangeHierarchical) { return; } KeyTreeView::setHierarchicalView(on); } void Page::setTemporary(bool on) { if (on == m_isTemporary) { return; } m_isTemporary = on; if (on) { setKeyFilter(std::shared_ptr()); } } // // // TabWidget // // class TabWidget::Private { friend class ::Kleo::TabWidget; TabWidget *const q; public: explicit Private(TabWidget *qq); ~Private() {} private: void slotContextMenu(const QPoint &p); void currentIndexChanged(int index); void slotPageTitleChanged(const QString &title); void slotPageKeyFilterChanged(const std::shared_ptr &filter); void slotPageStringFilterChanged(const QString &filter); void slotPageHierarchyChanged(bool on); #ifndef QT_NO_INPUTDIALOG void slotRenameCurrentTab() { renamePage(currentPage()); } #endif // QT_NO_INPUTDIALOG void slotNewTab(); void slotDuplicateCurrentTab() { duplicatePage(currentPage()); } void slotCloseCurrentTab() { closePage(currentPage()); } void slotMoveCurrentTabLeft() { movePageLeft(currentPage()); } void slotMoveCurrentTabRight() { movePageRight(currentPage()); } void slotToggleHierarchicalView(bool on) { toggleHierarchicalView(currentPage(), on); } void slotExpandAll() { expandAll(currentPage()); } void slotCollapseAll() { collapseAll(currentPage()); } #ifndef QT_NO_INPUTDIALOG void renamePage(Page *page); #endif void duplicatePage(Page *page); void closePage(Page *page); void movePageLeft(Page *page); void movePageRight(Page *page); void toggleHierarchicalView(Page *page, bool on); void expandAll(Page *page); void collapseAll(Page *page); void enableDisableCurrentPageActions(); void enableDisablePageActions(const std::vector &actions, const Page *page); Page *currentPage() const { Q_ASSERT(!tabWidget.currentWidget() || qobject_cast(tabWidget.currentWidget())); return static_cast(tabWidget.currentWidget()); } Page *page(unsigned int idx) const { Q_ASSERT(!tabWidget.widget(idx) || qobject_cast(tabWidget.widget(idx))); return static_cast(tabWidget.widget(idx)); } Page *senderPage() const { QObject *const sender = q->sender(); Q_ASSERT(!sender || qobject_cast(sender)); return static_cast(sender); } bool isSenderCurrentPage() const { Page *const sp = senderPage(); return sp && sp == currentPage(); } QTreeView *addView(Page *page, Page *columnReference); void setCornerAction(QAction *action, Qt::Corner corner); private: AbstractKeyListModel *flatModel; AbstractKeyListModel *hierarchicalModel; QTabWidget tabWidget; QVBoxLayout layout; enum { Rename, Duplicate, Close, MoveLeft, MoveRight, Hierarchical, ExpandAll, CollapseAll, NumPageActions }; QAction *newAction = nullptr; std::vector currentPageActions; std::vector otherPageActions; bool actionsCreated; }; TabWidget::Private::Private(TabWidget *qq) : q(qq), flatModel(nullptr), hierarchicalModel(nullptr), tabWidget(q), layout(q), actionsCreated(false) { KDAB_SET_OBJECT_NAME(tabWidget); KDAB_SET_OBJECT_NAME(layout); layout.setContentsMargins(0, 0, 0, 0); layout.addWidget(&tabWidget); tabWidget.tabBar()->hide(); tabWidget.setMovable(true); tabWidget.tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); connect(&tabWidget, SIGNAL(currentChanged(int)), q, SLOT(currentIndexChanged(int))); connect(tabWidget.tabBar(), &QWidget::customContextMenuRequested, q, [this](const QPoint & p) { slotContextMenu(p); }); } void TabWidget::Private::slotContextMenu(const QPoint &p) { const int tabUnderPos = tabWidget.tabBar()->tabAt(p); Page *const contextMenuPage = static_cast(tabWidget.widget(tabUnderPos)); const Page *const current = currentPage(); const std::vector actions = contextMenuPage == current ? currentPageActions : otherPageActions; enableDisablePageActions(actions, contextMenuPage); QMenu menu; menu.addAction(actions[Rename]); menu.addSeparator(); menu.addAction(newAction); menu.addAction(actions[Duplicate]); menu.addSeparator(); menu.addAction(actions[MoveLeft]); menu.addAction(actions[MoveRight]); menu.addSeparator(); menu.addAction(actions[Close]); const QAction *const action = menu.exec(tabWidget.tabBar()->mapToGlobal(p)); if (contextMenuPage == current || action == newAction) { return; // performed through signal/slot connections... } #ifndef QT_NO_INPUTDIALOG if (action == otherPageActions[Rename]) { renamePage(contextMenuPage); } #endif // QT_NO_INPUTDIALOG else if (action == otherPageActions[Duplicate]) { duplicatePage(contextMenuPage); } else if (action == otherPageActions[Close]) { closePage(contextMenuPage); } else if (action == otherPageActions[MoveLeft]) { movePageLeft(contextMenuPage); } else if (action == otherPageActions[MoveRight]) { movePageRight(contextMenuPage); } } void TabWidget::Private::currentIndexChanged(int index) { const Page *const page = this->page(index); Q_EMIT q->currentViewChanged(page ? page->view() : nullptr); Q_EMIT q->keyFilterChanged(page ? page->keyFilter() : std::shared_ptr()); Q_EMIT q->stringFilterChanged(page ? page->stringFilter() : QString()); enableDisableCurrentPageActions(); } void TabWidget::Private::enableDisableCurrentPageActions() { const Page *const page = currentPage(); Q_EMIT q->enableChangeStringFilter(page && page->canChangeStringFilter()); Q_EMIT q->enableChangeKeyFilter(page && page->canChangeKeyFilter()); enableDisablePageActions(currentPageActions, page); } void TabWidget::Private::enableDisablePageActions(const std::vector &actions, const Page *p) { actions[Rename] ->setEnabled(p && p->canBeRenamed()); actions[Duplicate] ->setEnabled(p); actions[Close] ->setEnabled(p && p->canBeClosed() && tabWidget.count() > 1); actions[MoveLeft] ->setEnabled(p && tabWidget.indexOf(const_cast(p)) != 0); actions[MoveRight] ->setEnabled(p && tabWidget.indexOf(const_cast(p)) != tabWidget.count() - 1); actions[Hierarchical]->setEnabled(p && p->canChangeHierarchical()); actions[Hierarchical]->setChecked(p && p->isHierarchicalView()); actions[ExpandAll] ->setEnabled(p && p->isHierarchicalView()); actions[CollapseAll] ->setEnabled(p && p->isHierarchicalView()); if (tabWidget.count() < 2) { tabWidget.tabBar()->hide(); } else { tabWidget.tabBar()->show(); } } void TabWidget::Private::slotPageTitleChanged(const QString &) { if (Page *const page = senderPage()) { const int idx = tabWidget.indexOf(page); tabWidget.setTabText(idx, page->title()); tabWidget.setTabToolTip(idx, page->toolTip()); } } void TabWidget::Private::slotPageKeyFilterChanged(const std::shared_ptr &kf) { if (isSenderCurrentPage()) { Q_EMIT q->keyFilterChanged(kf); } } void TabWidget::Private::slotPageStringFilterChanged(const QString &filter) { if (isSenderCurrentPage()) { Q_EMIT q->stringFilterChanged(filter); } } void TabWidget::Private::slotPageHierarchyChanged(bool) { enableDisableCurrentPageActions(); } void TabWidget::Private::slotNewTab() { const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", tabWidget.count())); Page *page = new Page(QString(), QStringLiteral("all-certificates"), QString(), nullptr, QString(), nullptr, group); addView(page, currentPage()); tabWidget.setCurrentIndex(tabWidget.count() - 1); } void TabWidget::Private::renamePage(Page *page) { if (!page) { return; } bool ok; const QString text = QInputDialog::getText(q, i18n("Rename Tab"), i18n("New tab title:"), QLineEdit::Normal, page->title(), &ok); if (!ok) { return; } page->setTitle(text); } void TabWidget::Private::duplicatePage(Page *page) { if (!page) { return; } Page *const clone = page->clone(); Q_ASSERT(clone); clone->liftAllRestrictions(); addView(clone, page); } void TabWidget::Private::closePage(Page *page) { if (!page || !page->canBeClosed() || tabWidget.count() <= 1) { return; } Q_EMIT q->viewAboutToBeRemoved(page->view()); tabWidget.removeTab(tabWidget.indexOf(page)); enableDisableCurrentPageActions(); } void TabWidget::Private::movePageLeft(Page *page) { if (!page) { return; } const int idx = tabWidget.indexOf(page); if (idx <= 0) { return; } tabWidget.tabBar()->moveTab(idx, idx - 1); enableDisableCurrentPageActions(); } void TabWidget::Private::movePageRight(Page *page) { if (!page) { return; } const int idx = tabWidget.indexOf(page); if (idx < 0 || idx >= tabWidget.count() - 1) { return; } tabWidget.tabBar()->moveTab(idx, idx + 1); enableDisableCurrentPageActions(); } void TabWidget::Private::toggleHierarchicalView(Page *page, bool on) { if (!page) { return; } page->setHierarchicalView(on); } void TabWidget::Private::expandAll(Page *page) { if (!page || !page->view()) { return; } page->view()->expandAll(); } void TabWidget::Private::collapseAll(Page *page) { if (!page || !page->view()) { return; } page->view()->collapseAll(); } TabWidget::TabWidget(QWidget *p, Qt::WindowFlags f) : QWidget(p, f), d(new Private(this)) { } TabWidget::~TabWidget() { saveViews(KSharedConfig::openConfig().data()); } void TabWidget::setFlatModel(AbstractKeyListModel *model) { if (model == d->flatModel) { return; } d->flatModel = model; for (unsigned int i = 0, end = count(); i != end; ++i) if (Page *const page = d->page(i)) { page->setFlatModel(model); } } AbstractKeyListModel *TabWidget::flatModel() const { return d->flatModel; } void TabWidget::setHierarchicalModel(AbstractKeyListModel *model) { if (model == d->hierarchicalModel) { return; } d->hierarchicalModel = model; for (unsigned int i = 0, end = count(); i != end; ++i) if (Page *const page = d->page(i)) { page->setHierarchicalModel(model); } } AbstractKeyListModel *TabWidget::hierarchicalModel() const { return d->hierarchicalModel; } void TabWidget::Private::setCornerAction(QAction *action, Qt::Corner corner) { if (!action) { return; } auto b = new QToolButton; b->setDefaultAction(action); tabWidget.setCornerWidget(b, corner); } void TabWidget::setStringFilter(const QString &filter) { if (Page *const page = d->currentPage()) { page->setStringFilter(filter); } } void TabWidget::setKeyFilter(const std::shared_ptr &filter) { if (!filter) { qCDebug(KLEOPATRA_LOG) << "TabWidget::setKeyFilter() trial to set filter=NULL"; return; } if (Page *const page = d->currentPage()) { page->setKeyFilter(filter); } } std::vector TabWidget::views() const { std::vector result; const unsigned int N = count(); result.reserve(N); for (unsigned int i = 0; i != N; ++i) if (const Page *const p = d->page(i)) { result.push_back(p->view()); } return result; } QAbstractItemView *TabWidget::currentView() const { if (Page *const page = d->currentPage()) { return page->view(); } else { return nullptr; } } KeyListModelInterface *TabWidget::currentModel() const { const QAbstractItemView *const view = currentView(); if (!view) { return nullptr; } auto const proxy = qobject_cast(view->model()); if (!proxy) { return nullptr; } return dynamic_cast(proxy); } unsigned int TabWidget::count() const { return d->tabWidget.count(); } void TabWidget::setMultiSelection(bool on) { for (unsigned int i = 0, end = count(); i != end; ++i) if (const Page *const p = d->page(i)) if (QTreeView *const view = p->view()) { view->setSelectionMode(on ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection); } } void TabWidget::createActions(KActionCollection *coll) { if (!coll) { return; } const action_data actionDataNew = { "window_new_tab", i18n("New Tab"), i18n("Open a new tab"), "tab-new-background", this, SLOT(slotNewTab()), QStringLiteral("CTRL+SHIFT+N"), false, true }; d->newAction = make_action_from_data(actionDataNew, coll); struct action_data actionData[] = { { "window_rename_tab", i18n("Rename Tab..."), i18n("Rename this tab"), "edit-rename", this, SLOT(slotRenameCurrentTab()), QStringLiteral("CTRL+SHIFT+R"), false, false }, { "window_duplicate_tab", i18n("Duplicate Tab"), i18n("Duplicate this tab"), "tab-duplicate", this, SLOT(slotDuplicateCurrentTab()), QStringLiteral("CTRL+SHIFT+D"), false, true }, { "window_close_tab", i18n("Close Tab"), i18n("Close this tab"), "tab-close", this, SLOT(slotCloseCurrentTab()), QStringLiteral("CTRL+SHIFT+W"), false, false }, // ### CTRL-W when available { "window_move_tab_left", i18n("Move Tab Left"), i18n("Move this tab left"), nullptr, this, SLOT(slotMoveCurrentTabLeft()), QStringLiteral("CTRL+SHIFT+LEFT"), false, false }, { "window_move_tab_right", i18n("Move Tab Right"), i18n("Move this tab right"), nullptr, this, SLOT(slotMoveCurrentTabRight()), QStringLiteral("CTRL+SHIFT+RIGHT"), false, false }, { "window_view_hierarchical", i18n("Hierarchical Certificate List"), QString(), nullptr, this, SLOT(slotToggleHierarchicalView(bool)), QString(), true, false }, { "window_expand_all", i18n("Expand All"), QString(), nullptr, this, SLOT(slotExpandAll()), QStringLiteral("CTRL+."), false, false }, { "window_collapse_all", i18n("Collapse All"), QString(), nullptr, this, SLOT(slotCollapseAll()), QStringLiteral("CTRL+,"), false, false }, }; d->currentPageActions.reserve(d->NumPageActions); for (int i = 0; i < d->NumPageActions; ++i) { d->currentPageActions.push_back(make_action_from_data(actionData[i], coll)); } d->otherPageActions.reserve(d->NumPageActions); for (int i = 0; i < d->NumPageActions; ++i) { // create actions for the context menu of the currently not active tabs, // but do not add those actions to the action collection const action_data ad = actionData[i]; auto action = new QAction(ad.text, coll); if (ad.icon) { action->setIcon(QIcon::fromTheme(QLatin1String(ad.icon))); } action->setEnabled(ad.enabled); d->otherPageActions.push_back(action); } d->setCornerAction(d->newAction, Qt::TopLeftCorner); d->setCornerAction(d->currentPageActions[d->Close], Qt::TopRightCorner); d->actionsCreated = true; } QAbstractItemView *TabWidget::addView(const QString &title, const QString &id, const QString &text) { const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", d->tabWidget.count())); Page *page = new Page(title, id, text, nullptr, QString(), nullptr, group); return d->addView(page, d->currentPage()); } QAbstractItemView *TabWidget::addView(const KConfigGroup &group) { return d->addView(new Page(group), nullptr); } QAbstractItemView *TabWidget::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip) { const KConfigGroup group = KSharedConfig::openConfig()->group("KeyTreeView_default"); Page *const page = new Page(title, QString(), QString(), proxy, tabToolTip, nullptr, group); page->setTemporary(true); QAbstractItemView *v = d->addView(page, d->currentPage()); d->tabWidget.setCurrentIndex(d->tabWidget.count() - 1); return v; } QTreeView *TabWidget::Private::addView(Page *page, Page *columnReference) { if (!page) { return nullptr; } if (!actionsCreated) { auto coll = new KActionCollection(q); q->createActions(coll); } page->setFlatModel(flatModel); page->setHierarchicalModel(hierarchicalModel); connect(page, SIGNAL(titleChanged(QString)), q, SLOT(slotPageTitleChanged(QString))); connect(page, SIGNAL(keyFilterChanged(std::shared_ptr)), q, SLOT(slotPageKeyFilterChanged(std::shared_ptr))); connect(page, SIGNAL(stringFilterChanged(QString)), q, SLOT(slotPageStringFilterChanged(QString))); connect(page, SIGNAL(hierarchicalChanged(bool)), q, SLOT(slotPageHierarchyChanged(bool))); if (columnReference) { page->setColumnSizes(columnReference->columnSizes()); page->setSortColumn(columnReference->sortColumn(), columnReference->sortOrder()); } QAbstractItemView *const previous = q->currentView(); const int tabIndex = tabWidget.addTab(page, page->title()); tabWidget.setTabToolTip(tabIndex, page->toolTip()); // work around a bug in QTabWidget (tested with 4.3.2) not emitting currentChanged() when the first widget is inserted QAbstractItemView *const current = q->currentView(); if (previous != current) { currentIndexChanged(tabWidget.currentIndex()); } enableDisableCurrentPageActions(); QTreeView *view = page->view(); Q_EMIT q->viewAdded(view); return view; } static QStringList extractViewGroups(const KConfig *config) { return config ? config->groupList().filter(QRegularExpression(QStringLiteral("^View #\\d+$"))) : QStringList(); } // work around deleteGroup() not deleting groups out of groupList(): static const bool KCONFIG_DELETEGROUP_BROKEN = true; void TabWidget::loadViews(const KConfig *config) { if (config) { QStringList groupList = extractViewGroups(config); groupList.sort(); - for (const QString &group : qAsConst(groupList)) { + for (const QString &group : std::as_const(groupList)) { const KConfigGroup kcg(config, group); if (!KCONFIG_DELETEGROUP_BROKEN || kcg.readEntry("magic", 0U) == 0xFA1AFE1U) { addView(kcg); } } } if (!count()) { // add default view: addView(QString(), QStringLiteral("all-certificates")); } } void TabWidget::saveViews(KConfig *config) const { if (!config) { return; } Q_FOREACH (const QString &group, extractViewGroups(config)) { config->deleteGroup(group); } unsigned int vg = 0; for (unsigned int i = 0, end = count(); i != end; ++i) { if (const Page *const p = d->page(i)) { if (p->isTemporary()) { continue; } KConfigGroup group(config, QString::asprintf("View #%u", vg++)); p->saveTo(group); if (KCONFIG_DELETEGROUP_BROKEN) { group.writeEntry("magic", 0xFA1AFE1U); } } } } static void xconnect(const QObject *o1, const char *signal, const QObject *o2, const char *slot) { QObject::connect(o1, signal, o2, slot); QObject::connect(o2, signal, o1, slot); } void TabWidget::connectSearchBar(QObject *sb) { xconnect(sb, SIGNAL(stringFilterChanged(QString)), this, SLOT(setStringFilter(QString))); xconnect(sb, SIGNAL(keyFilterChanged(std::shared_ptr)), this, SLOT(setKeyFilter(std::shared_ptr))); connect(this, SIGNAL(enableChangeStringFilter(bool)), sb, SLOT(setChangeStringFilterEnabled(bool))); connect(this, SIGNAL(enableChangeKeyFilter(bool)), sb, SLOT(setChangeKeyFilterEnabled(bool))); } #include "moc_tabwidget.cpp" #include "tabwidget.moc" diff --git a/tests/test_uiserver.cpp b/tests/test_uiserver.cpp index 615d0d1e3..f9e3c4c77 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 --verify-detached // #include #include #include #include #include "utils/wsastarter.h" #include "utils/hex.h" #ifndef Q_OS_WIN32 # include # include # include # include # include #endif #include #include #include #include #include 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 inFDs, outFDs, msgFDs; static std::vector inFiles, outFiles, msgFiles; static std::map inquireData; static void usage(const std::string &msg = std::string()) { std::cerr << msg << std::endl << "\n" "Usage: test_uiserver [] [] [] command []\n" "where:\n" #ifdef Q_OS_WIN32 " : [--input[-fd] ] [--output[-fd] ] [--message[-fd] ]\n" #else " : [--input ] [--output ] [--message ]\n" #endif " : *[--option name=value]\n" " : [--inquire keyword=]\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::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 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::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::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::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::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::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::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; } } - for (const char *opt : qAsConst(options)) { + for (const char *opt : std::as_const(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; }