diff --git a/src/commands/command_p.h b/src/commands/command_p.h index 3e3f47ce2..be81eee62 100644 --- a/src/commands/command_p.h +++ b/src/commands/command_p.h @@ -1,144 +1,144 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/command_p.h This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef __KLEOPATRA_COMMANDS_COMMAND_P_H__ #define __KLEOPATRA_COMMANDS_COMMAND_P_H__ #include "command.h" #include "view/keylistcontroller.h" #include #include #include #include #include #include #include #include #include class Kleo::Command::Private { friend class ::Kleo::Command; protected: Command *const q; public: explicit Private(Command *qq, KeyListController *controller); virtual ~Private(); QAbstractItemView *view() const { return view_; } QWidget *parentWidgetOrView() const { if (parentWidget_) { return parentWidget_; } else { return view_; } } KeyListModelInterface *model() const { return view_ ? dynamic_cast(view_->model()) : nullptr; } KeyListController *controller() const { return controller_; } QList indexes() const { QList result; std::copy(indexes_.begin(), indexes_.end(), std::back_inserter(result)); return result; } GpgME::Key key() const { return keys_.empty() ? model() && !indexes_.empty() ? model()->key(indexes_.front()) : GpgME::Key::null : keys_.front(); } std::vector keys() const { return keys_.empty() ? model() ? model()->keys(indexes()) : std::vector() : keys_; } void finished() { Q_EMIT q->finished(); if (autoDelete) { q->deleteLater(); } } void canceled() { Q_EMIT q->canceled(); finished(); } void error(const QString &text, const QString &caption = QString(), KMessageBox::Options options = KMessageBox::Notify) const { if (parentWId) { KMessageBox::errorWId(parentWId, text, caption, options); } else { KMessageBox::error(parentWidgetOrView(), text, caption, options); } } void information(const QString &text, const QString &caption = QString(), const QString &dontShowAgainName = QString(), KMessageBox::Options options = KMessageBox::Notify) const { if (parentWId) { KMessageBox::informationWId(parentWId, text, caption, dontShowAgainName, options); } else { KMessageBox::information(parentWidgetOrView(), text, caption, dontShowAgainName, options); } } void applyWindowID(QWidget *w) const { - return q->applyWindowID(w); + q->applyWindowID(w); } private: bool autoDelete : 1; bool warnWhenRunningAtShutdown : 1; std::vector keys_; QList indexes_; QPointer view_; QPointer parentWidget_; WId parentWId; QPointer controller_; }; #endif /* __KLEOPATRA_COMMANDS_COMMAND_P_H__ */ diff --git a/src/commands/importpaperkeycommand.cpp b/src/commands/importpaperkeycommand.cpp index 6b1c8ec2e..fbb15515b 100644 --- a/src/commands/importpaperkeycommand.cpp +++ b/src/commands/importpaperkeycommand.cpp @@ -1,247 +1,247 @@ /* commands/importperkeycommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "importpaperkeycommand.h" #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "command_p.h" using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; ImportPaperKeyCommand::ImportPaperKeyCommand(const GpgME::Key &k) : GnuPGProcessCommand(k) { } QStringList ImportPaperKeyCommand::arguments() const { const Key key = d->key(); QStringList result; result << paperKeyInstallPath() << QStringLiteral("--pubring") << mTmpDir.path() + QStringLiteral("/pubkey.gpg") << QStringLiteral("--secrets") << mTmpDir.path() + QStringLiteral("/secrets.txt") << QStringLiteral("--output") << mTmpDir.path() + QStringLiteral("/seckey.gpg"); return result; } void ImportPaperKeyCommand::exportResult(const GpgME::Error &err, const QByteArray &data) { if (err) { d->error(QString::fromUtf8(err.asString()), errorCaption()); d->finished(); return; } if (!mTmpDir.isValid()) { // Should not happen so no i18n d->error(QStringLiteral("Failed to get temporary directory"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to get temporary dir"; d->finished(); return; } const QString fileName = mTmpDir.path() + QStringLiteral("/pubkey.gpg"); QFile f(fileName); if (!f.open(QIODevice::WriteOnly)) { d->error(QStringLiteral("Failed to create temporary file"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file"; d->finished(); return; } f.write(data); f.close(); // Copy and sanitize input a bit QFile input(mFileName); if (!input.open(QIODevice::ReadOnly)) { d->error(xi18n("Cannot open %1 for reading.", mFileName), errorCaption()); d->finished(); return; } const QString outName = mTmpDir.path() + QStringLiteral("/secrets.txt"); QFile out(outName); if (!out.open(QIODevice::WriteOnly)) { // Should not happen d->error(QStringLiteral("Failed to create temporary file"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file for writing"; d->finished(); return; } QTextStream in(&input); while (!in.atEnd()) { // Paperkey is picky, tabs may not be part. Neither may be empty lines. const QString line = in.readLine().trimmed().replace(QLatin1Char('\t'), QStringLiteral(" ")) + QLatin1Char('\n'); out.write(line.toUtf8()); } input.close(); out.close(); GnuPGProcessCommand::doStart(); } void ImportPaperKeyCommand::postSuccessHook(QWidget *) { qCDebug(KLEOPATRA_LOG) << "Paperkey secrets restore finished successfully."; QFile secKey(mTmpDir.path() + QStringLiteral("/seckey.gpg")); if (!secKey.open(QIODevice::ReadOnly)) { d->error(QStringLiteral("Failed to open temporary secret"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file"; - finished(); + Q_EMIT finished(); return; } auto data = secKey.readAll(); secKey.close(); auto importjob = QGpgME::openpgp()->importJob(); auto result = importjob->exec(data); delete importjob; if (result.error()) { d->error(QString::fromUtf8(result.error().asString()), errorCaption()); - finished(); + Q_EMIT finished(); return; } if (!result.numSecretKeysImported() || (result.numSecretKeysUnchanged() == result.numSecretKeysImported())) { d->error(i18n("Failed to restore any secret keys."), errorCaption()); - finished(); + Q_EMIT finished(); return; } // Refresh the key after success KeyCache::mutableInstance()->reload(OpenPGP); - finished(); + Q_EMIT finished(); d->information(xi18nc("@info", "Successfully restored the secret key parts from %1", mFileName)); return; } void ImportPaperKeyCommand::doStart() { if (paperKeyInstallPath().isNull()) { KMessageBox::sorry(d->parentWidgetOrView(), xi18nc("@info", "Kleopatra uses " "PaperKey to import your " "text backup." "Please make sure it is installed."), i18nc("@title", "Failed to find PaperKey executable.")); return; } mFileName = QFileDialog::getOpenFileName(d->parentWidgetOrView(), i18n("Select input file"), QString(), QStringLiteral("%1 (*.txt)").arg(i18n("Paper backup")) #ifdef Q_OS_WIN /* For whatever reason at least with Qt 5.6.1 the native file dialog crashes in * my (aheinecke) Windows 10 environment when invoked here. * In other places it works, with the same arguments as in other places (e.g. import) * it works. But not here. Maybe it's our (gpg4win) build? But why did it only * crash here? * * It does not crash immediately, the program flow continues for a while before it * crashes so this is hard to debug. * * There are some reports about this * QTBUG-33119 QTBUG-41416 where different people describe "bugs" but they * describe them differently also not really reproducible. * Anyway this works for now and for such an exotic feature its good enough for now. */ , 0, QFileDialog::DontUseNativeDialog #endif ); if (mFileName.isEmpty()) { d->finished(); return; } auto exportJob = QGpgME::openpgp()->publicKeyExportJob(); // Do not change to new style connect without testing on // Windows / mingw first for compatibility please. connect(exportJob, &QGpgME::ExportJob::result, this, &ImportPaperKeyCommand::exportResult); exportJob->start(QStringList() << QLatin1String(d->key().primaryFingerprint())); } QString ImportPaperKeyCommand::errorCaption() const { return i18nc("@title:window", "Error importing secret key"); } QString ImportPaperKeyCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG process that tried to restore the secret key " "ended prematurely because of an unexpected error." "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString ImportPaperKeyCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "An error occurred while trying to restore the secret key. " "The output from %1 was:" "%2", args[0], errorString()); } QString ImportPaperKeyCommand::successMessage(const QStringList &) const { return QString(); } diff --git a/src/crypto/decryptverifyfilescontroller.cpp b/src/crypto/decryptverifyfilescontroller.cpp index a2556a437..c47759dfe 100644 --- a/src/crypto/decryptverifyfilescontroller.cpp +++ b/src/crypto/decryptverifyfilescontroller.cpp @@ -1,468 +1,468 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifyfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #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 #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 std::vector >::const_iterator 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: m_passedFiles) { + for (const auto &fname: qAsConst(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/gui/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp index ebfa02edf..419f20d98 100644 --- a/src/crypto/gui/signencryptwidget.cpp +++ b/src/crypto/gui/signencryptwidget.cpp @@ -1,544 +1,544 @@ /* crypto/gui/signencryptwidget.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2016 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include "signencryptwidget.h" #include "kleopatra_debug.h" #include "certificatelineedit.h" #include "unknownrecipientwidget.h" #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) { QVBoxLayout *lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); mModel->useKeyCache(true, false); /* The signature selection */ QHBoxLayout *sigLay = new QHBoxLayout; QGroupBox *sigGrp = new QGroupBox(i18n("Prove authenticity (sign)")); mSigChk = new QCheckBox(i18n("Sign as:")); mSigChk->setChecked(true); mSigSelect = new KeySelectionCombo(); sigLay->addWidget(mSigChk); sigLay->addWidget(mSigSelect, 1); sigGrp->setLayout(sigLay); lay->addWidget(sigGrp); connect(mSigChk, &QCheckBox::toggled, mSigSelect, &QWidget::setEnabled); connect(mSigChk, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp); connect(mSigSelect, &KeySelectionCombo::currentKeyChanged, this, &SignEncryptWidget::updateOp); // Recipient selection mRecpLayout = new QGridLayout; mRecpLayout->setAlignment(Qt::AlignTop); QVBoxLayout *encBoxLay = new QVBoxLayout; QGroupBox *encBox = new QGroupBox(i18nc("@action", "Encrypt")); encBox->setLayout(encBoxLay); encBox->setAlignment(Qt::AlignLeft); // Own key mSelfSelect = new KeySelectionCombo(); mEncSelfChk = new QCheckBox(i18n("Encrypt for me:")); mEncSelfChk->setChecked(true); mRecpLayout->addWidget(mEncSelfChk, 0, 0); mRecpLayout->addWidget(mSelfSelect, 0, 1); // Checkbox for other keys mEncOtherChk = new QCheckBox(i18n("Encrypt for others:")); mRecpLayout->addWidget(mEncOtherChk, 1, 0); mEncOtherChk->setChecked(true); connect(mEncOtherChk, &QCheckBox::toggled, this, [this](bool toggled) { Q_FOREACH (CertificateLineEdit *edit, mRecpWidgets) { edit->setEnabled(toggled); } updateOp(); }); // Scroll area for other keys QWidget *recipientWidget = new QWidget; QScrollArea *recipientScroll = new QScrollArea; recipientWidget->setLayout(mRecpLayout); recipientScroll->setWidget(recipientWidget); recipientScroll->setWidgetResizable(true); recipientScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); recipientScroll->setFrameStyle(QFrame::NoFrame); mRecpLayout->setContentsMargins(0, 0, 0, 0); encBoxLay->addWidget(recipientScroll, 1); auto bar = recipientScroll->verticalScrollBar(); connect (bar, &QScrollBar::rangeChanged, this, [bar] (int, int max) { bar->setValue(max); }); // Checkbox for password mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data.")); mSymmetric->setToolTip(i18nc("Tooltip information for symetric encryption", "Additionally to the keys of the recipients you can encrypt your data with a password. " "Anyone who has the password can read the data without any secret key. " "Using a password is 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); addRecipient(Key()); updateOp(); } void SignEncryptWidget::addRecipient() { addRecipient(Key()); } void SignEncryptWidget::addRecipient(const Key &key) { CertificateLineEdit *certSel = new CertificateLineEdit(mModel, this, new EncryptCertificateFilter(mCurrentProto)); mRecpWidgets << certSel; if (!mRecpLayout->itemAtPosition(mRecpRowCount - 1, 1)) { // First widget. Should align with the row above that // contains the encrypt for others checkbox. mRecpLayout->addWidget(certSel, mRecpRowCount - 1, 1); } else { mRecpLayout->addWidget(certSel, mRecpRowCount++, 1); } connect(certSel, &CertificateLineEdit::keyChanged, this, &SignEncryptWidget::recipientsChanged); connect(certSel, &CertificateLineEdit::wantsRemoval, this, &SignEncryptWidget::recpRemovalRequested); connect(certSel, &CertificateLineEdit::editingStarted, this, static_cast(&SignEncryptWidget::addRecipient)); connect(certSel, &CertificateLineEdit::addRequested, this, static_cast(&SignEncryptWidget::addRecipient)); if (!key.isNull()) { certSel->setKey(key); mAddedKeys << key; } } void SignEncryptWidget::clearAddedRecipients() { - for (auto w: mUnknownWidgets) { + for (auto w: qAsConst(mUnknownWidgets)) { mRecpLayout->removeWidget(w); delete w; } - for (auto &key: mAddedKeys) { + for (auto &key: qAsConst(mAddedKeys)) { removeRecipient(key); } } void SignEncryptWidget::addUnknownRecipient(const char *keyID) { auto unknownWidget = new UnknownRecipientWidget(keyID); mUnknownWidgets << unknownWidget; if (!mRecpLayout->itemAtPosition(mRecpRowCount - 1, 1)) { // First widget. Should align with the row above that // contains the encrypt for others checkbox. mRecpLayout->addWidget(unknownWidget, mRecpRowCount - 1, 1); } else { mRecpLayout->addWidget(unknownWidget, mRecpRowCount++, 1); } connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this] () { // Check if any unknown recipient can now be found. for (auto w: mUnknownWidgets) { auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData()); if (key.isNull()) { std::vector subids; subids.push_back(std::string(w->keyID().toLatin1().constData())); for (const auto &subkey: KeyCache::instance()->findSubkeysByKeyID(subids)) { key = subkey.parent(); } } if (key.isNull()) { continue; } // Key is now available replace by line edit. qCDebug(KLEOPATRA_LOG) << "Removing widget for keyid: " << w->keyID(); mRecpLayout->removeWidget(w); mUnknownWidgets.removeAll(w); delete w; addRecipient(key); } }); } void SignEncryptWidget::recipientsChanged() { bool oneEmpty = false; Q_FOREACH (const CertificateLineEdit *w, mRecpWidgets) { if (w->key().isNull()) { oneEmpty = true; break; } } if (!oneEmpty) { addRecipient(); } updateOp(); } Key SignEncryptWidget::signKey() const { if (mSigSelect->isEnabled()) { return mSigSelect->currentKey(); } return Key(); } Key SignEncryptWidget::selfKey() const { if (mSelfSelect->isEnabled()) { return mSelfSelect->currentKey(); } return Key(); } QVector SignEncryptWidget::recipients() const { QVector ret; Q_FOREACH (const CertificateLineEdit *w, mRecpWidgets) { if (!w->isEnabled()) { // If one is disabled, all are disabled. break; } const Key k = w->key(); if (!k.isNull()) { ret << k; } } const Key k = selfKey(); if (!k.isNull()) { ret << k; } return ret; } bool SignEncryptWidget::isDeVsAndValid() const { if (!signKey().isNull() && (!IS_DE_VS(signKey()) || keyValidity(signKey()) < GpgME::UserID::Validity::Full)) { return false; } if (!selfKey().isNull() && (!IS_DE_VS(selfKey()) || keyValidity(selfKey()) < GpgME::UserID::Validity::Full)) { return false; } for (const auto &key: recipients()) { if (!IS_DE_VS(key) || keyValidity(key) < GpgME::UserID::Validity::Full) { return false; } } return true; } void SignEncryptWidget::updateOp() { const Key sigKey = signKey(); const QVector recp = recipients(); QString newOp; if (!sigKey.isNull() && (!recp.isEmpty() || encryptSymmetric())) { newOp = i18nc("@action", "Sign / Encrypt"); } else if (!recp.isEmpty() || encryptSymmetric()) { newOp = i18nc("@action", "Encrypt"); } else if (!sigKey.isNull()) { newOp = i18nc("@action", "Sign"); } else { newOp = QString(); } mOp = newOp; Q_EMIT operationChanged(mOp); Q_EMIT keysChanged(); } QString SignEncryptWidget::currentOp() const { return mOp; } void SignEncryptWidget::recpRemovalRequested(CertificateLineEdit *w) { if (!w) { return; } int emptyEdits = 0; Q_FOREACH (const CertificateLineEdit *edit, mRecpWidgets) { 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: mRecpWidgets) { + for (CertificateLineEdit *edit: qAsConst(mRecpWidgets)) { const auto editKey = edit->key(); if (key.isNull() && editKey.isNull()) { recpRemovalRequested(edit); return; } if (editKey.primaryFingerprint() && key.primaryFingerprint() && !strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) { recpRemovalRequested(edit); return; } } } bool SignEncryptWidget::encryptSymmetric() const { return mSymmetric->isChecked(); } void SignEncryptWidget::loadKeys() { KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys"); auto cache = KeyCache::instance(); mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString())); mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString())); } void SignEncryptWidget::saveOwnKeys() const { KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys"); auto sigKey = mSigSelect->currentKey(); auto encKey = mSelfSelect->currentKey(); if (!sigKey.isNull()) { keys.writeEntry("SigningKey", sigKey.primaryFingerprint()); } if (!encKey.isNull()) { keys.writeEntry("EncryptKey", encKey.primaryFingerprint()); } } void SignEncryptWidget::setSigningChecked(bool value) { mSigChk->setChecked(value); } void SignEncryptWidget::setEncryptionChecked(bool value) { mEncSelfChk->setChecked(value); mEncOtherChk->setChecked(value); } void SignEncryptWidget::setProtocol(GpgME::Protocol proto) { if (mCurrentProto == proto) { return; } mCurrentProto = proto; mSigSelect->setKeyFilter(std::shared_ptr(new SignCertificateFilter(proto))); mSelfSelect->setKeyFilter(std::shared_ptr(new EncryptSelfCertificateFilter(proto))); const auto encFilter = std::shared_ptr(new EncryptCertificateFilter(proto)); Q_FOREACH (CertificateLineEdit *edit, 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: mRecpWidgets) { + for (const auto edit: qAsConst(mRecpWidgets)) { if (!edit->isEmpty() && edit->key().isNull()) { KMessageBox::error(this, i18nc("%1 is user input that could not be found", "Could not find a key for '%1'", edit->text().toHtmlEscaped()), i18n("Failed to find recipient"), KMessageBox::Notify); return false; } } return true; } diff --git a/src/uiserver/uiserver.cpp b/src/uiserver/uiserver.cpp index 478ecfc8f..851490642 100644 --- a/src/uiserver/uiserver.cpp +++ b/src/uiserver/uiserver.cpp @@ -1,305 +1,305 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/uiserver.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "uiserver.h" #include "uiserver_p.h" #include "sessiondata.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include using namespace Kleo; // static void UiServer::setLogStream(FILE *stream) { assuan_set_assuan_log_stream(stream); } UiServer::Private::Private(UiServer *qq) : QTcpServer(), q(qq), file(), factories(), connections(), suggestedSocketName(), actualSocketName(), cryptoCommandsEnabled(false) { #ifndef HAVE_ASSUAN2 assuan_set_assuan_err_source(GPG_ERR_SOURCE_DEFAULT); #else assuan_set_gpg_err_source(GPG_ERR_SOURCE_DEFAULT); assuan_sock_init(); #endif } bool UiServer::Private::isStaleAssuanSocket(const QString &fileName) { assuan_context_t ctx = nullptr; #ifndef HAVE_ASSUAN2 const bool error = assuan_socket_connect_ext(&ctx, QFile::encodeName(fileName).constData(), -1, 0); #else const bool error = assuan_new(&ctx) || assuan_socket_connect(ctx, QFile::encodeName(fileName).constData(), ASSUAN_INVALID_PID, 0); #endif if (!error) #ifndef HAVE_ASSUAN2 assuan_disconnect(ctx); #else assuan_release(ctx); #endif return error; } UiServer::UiServer(const QString &socket, QObject *p) : QObject(p), d(new Private(this)) { d->suggestedSocketName = d->makeFileName(socket); } UiServer::~UiServer() { if (QFile::exists(d->actualSocketName)) { QFile::remove(d->actualSocketName); } } namespace { using Iterator = std::vector>::iterator; -static bool empty(const std::pair &iters) +static bool empty(std::pair iters) { return iters.first == iters.second; } } bool UiServer::registerCommandFactory(const std::shared_ptr &cf) { if (cf && empty(std::equal_range(d->factories.begin(), d->factories.end(), cf, _detail::ByName()))) { d->factories.push_back(cf); std::inplace_merge(d->factories.begin(), d->factories.end() - 1, d->factories.end(), _detail::ByName()); return true; } else { if (!cf) { qCWarning(KLEOPATRA_LOG) << "NULL factory"; } else { qCWarning(KLEOPATRA_LOG) << (void *)cf.get() << " factory already registered"; } return false; } } void UiServer::start() { d->makeListeningSocket(); } void UiServer::stop() { d->close(); if (d->file.exists()) { d->file.remove(); } if (isStopped()) { SessionDataHandler::instance()->clear(); Q_EMIT stopped(); } } void UiServer::enableCryptoCommands(bool on) { if (on == d->cryptoCommandsEnabled) { return; } d->cryptoCommandsEnabled = on; std::for_each(d->connections.cbegin(), d->connections.cend(), [on](std::shared_ptr conn) { conn->enableCryptoCommands(on); }); } QString UiServer::socketName() const { return d->actualSocketName; } bool UiServer::waitForStopped(unsigned int ms) { if (isStopped()) { return true; } QEventLoop loop; QTimer timer; timer.setInterval(ms); timer.setSingleShot(true); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); connect(this, &UiServer::stopped, &loop, &QEventLoop::quit); loop.exec(); return !timer.isActive(); } bool UiServer::isStopped() const { return d->connections.empty() && !d->isListening(); } bool UiServer::isStopping() const { return !d->connections.empty() && !d->isListening(); } void UiServer::Private::slotConnectionClosed(Kleo::AssuanServerConnection *conn) { qCDebug(KLEOPATRA_LOG) << "UiServer: connection " << (void *)conn << " closed"; connections.erase(std::remove_if(connections.begin(), connections.end(), [conn](const std::shared_ptr &other) { return conn == other.get(); }), connections.end()); if (q->isStopped()) { SessionDataHandler::instance()->clear(); Q_EMIT q->stopped(); } } void UiServer::Private::incomingConnection(qintptr fd) { try { qCDebug(KLEOPATRA_LOG) << "UiServer: client connect on fd " << fd; #if defined(HAVE_ASSUAN_SOCK_GET_NONCE) || defined(HAVE_ASSUAN2) if (assuan_sock_check_nonce((assuan_fd_t)fd, &nonce)) { qCDebug(KLEOPATRA_LOG) << "UiServer: nonce check failed"; assuan_sock_close((assuan_fd_t)fd); return; } #endif const std::shared_ptr c(new AssuanServerConnection((assuan_fd_t)fd, factories)); connect(c.get(), &AssuanServerConnection::closed, this, &Private::slotConnectionClosed); connect(c.get(), &AssuanServerConnection::startKeyManagerRequested, q, &UiServer::startKeyManagerRequested, Qt::QueuedConnection); connect(c.get(), &AssuanServerConnection::startConfigDialogRequested, q, &UiServer::startConfigDialogRequested, Qt::QueuedConnection); c->enableCryptoCommands(cryptoCommandsEnabled); connections.push_back(c); qCDebug(KLEOPATRA_LOG) << "UiServer: client connection " << (void *)c.get() << " established successfully"; } catch (const Exception &e) { qCDebug(KLEOPATRA_LOG) << "UiServer: client connection failed: " << e.what(); QTcpSocket s; s.setSocketDescriptor(fd); QTextStream(&s) << "ERR " << e.error_code() << " " << e.what() << "\r\n"; s.waitForBytesWritten(); s.close(); } catch (...) { qCDebug(KLEOPATRA_LOG) << "UiServer: client connection failed: unknown exception caught"; // this should never happen... QTcpSocket s; s.setSocketDescriptor(fd); QTextStream(&s) << "ERR 63 unknown exception caught\r\n"; s.waitForBytesWritten(); s.close(); } } QString UiServer::Private::makeFileName(const QString &socket) const { if (!socket.isEmpty()) { return socket; } const QString gnupgHome = gnupgHomeDirectory(); if (gnupgHome.isEmpty()) { throw_(i18n("Could not determine the GnuPG home directory. Consider setting the GNUPGHOME environment variable.")); } ensureDirectoryExists(gnupgHome); const QDir dir(gnupgHome); Q_ASSERT(dir.exists()); return dir.absoluteFilePath(QStringLiteral("S.uiserver")); } void UiServer::Private::ensureDirectoryExists(const QString &path) const { const QFileInfo info(path); if (info.exists() && !info.isDir()) { throw_(i18n("Cannot determine the GnuPG home directory: %1 exists but is not a directory.", path)); } if (info.exists()) { return; } const QDir dummy; //there is no static QDir::mkpath()... errno = 0; if (!dummy.mkpath(path)) { throw_(i18n("Could not create GnuPG home directory %1: %2", path, systemErrorString())); } } void UiServer::Private::makeListeningSocket() { // First, create a file (we do this only for the name, gmpfh) const QString fileName = suggestedSocketName; if (QFile::exists(fileName)) { if (isStaleAssuanSocket(fileName)) { QFile::remove(fileName); } else { throw_(i18n("Detected another running gnupg UI server listening at %1.", fileName)); } } doMakeListeningSocket(QFile::encodeName(fileName)); actualSocketName = suggestedSocketName; } #include "moc_uiserver_p.cpp" diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp index 4fff69fd0..c29ee92a4 100644 --- a/src/view/keytreeview.cpp +++ b/src/view/keytreeview.cpp @@ -1,721 +1,721 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keytreeview.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2009 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "keytreeview.h" #include #include #include #include #include "utils/headerview.h" #include "utils/remarks.h" #include #include #include #include #include "kleopatra_debug.h" #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 #define REMARK_COLUMN 13 using namespace Kleo; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) namespace { class TreeView : public QTreeView { public: explicit TreeView(QWidget *parent = nullptr) : QTreeView(parent) { header()->installEventFilter(this); } QSize minimumSizeHint() const override { const QSize min = QTreeView::minimumSizeHint(); return QSize(min.width(), min.height() + 5 * fontMetrics().height()); } protected: bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if (event->type() == QEvent::ContextMenu) { QContextMenuEvent *e = static_cast(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 == REMARK_COLUMN) { Remarks::enableRemarks(action->isChecked()); } if (action->isChecked()) { showColumn(col); } else { hideColumn(col); } KeyTreeView *tv = qobject_cast (parent()); if (tv) { tv->resizeColumns(); } }); } foreach (QAction *action, mColumnActions) { int column = action->data().toInt(); action->setChecked(!isColumnHidden(column)); } mHeaderPopup->popup(mapToGlobal(e->pos())); return true; } return false; } private: QMenu *mHeaderPopup = nullptr; QList mColumnActions; }; } // anon namespace KeyTreeView::KeyTreeView(QWidget *parent) : QWidget(parent), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(nullptr), m_view(new TreeView(this)), m_flatModel(nullptr), m_hierarchicalModel(nullptr), m_stringFilter(), m_keyFilter(), m_isHierarchical(true) { init(); } KeyTreeView::KeyTreeView(const KeyTreeView &other) : QWidget(nullptr), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr), m_view(new TreeView(this)), m_flatModel(other.m_flatModel), m_hierarchicalModel(other.m_hierarchicalModel), m_stringFilter(other.m_stringFilter), m_keyFilter(other.m_keyFilter), m_group(other.m_group), m_isHierarchical(other.m_isHierarchical) { init(); setColumnSizes(other.columnSizes()); setSortColumn(other.sortColumn(), other.sortOrder()); } KeyTreeView::KeyTreeView(const QString &text, const std::shared_ptr &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 (HeaderView *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 (HeaderView *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()) { m_group = KSharedConfig::openConfig()->group("KeyTreeView_default"); } else { // Reopen as non const KConfig *conf = m_group.config(); m_group = conf->group(m_group.name()); } if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) { KDAB_SET_OBJECT_NAME(m_additionalProxy); } QLayout *layout = new QVBoxLayout(this); KDAB_SET_OBJECT_NAME(layout); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_view); HeaderView *headerView = new HeaderView(Qt::Horizontal); KDAB_SET_OBJECT_NAME(headerView); headerView->installEventFilter(m_view); headerView->setSectionsMovable(true); m_view->setHeader(headerView); m_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); //m_view->setAlternatingRowColors( true ); m_view->setAllColumnsShowFocus(true); m_view->setSortingEnabled(true); if (model()) { if (m_additionalProxy) { m_additionalProxy->setSourceModel(model()); } else { m_proxy->setSourceModel(model()); } } if (m_additionalProxy) { m_proxy->setSourceModel(m_additionalProxy); if (!m_additionalProxy->parent()) { m_additionalProxy->setParent(this); } } m_proxy->setFilterFixedString(m_stringFilter); m_proxy->setKeyFilter(m_keyFilter); m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); KeyRearrangeColumnsProxyModel *rearangingModel = new KeyRearrangeColumnsProxyModel(this); rearangingModel->setSourceModel(m_proxy); rearangingModel->setSourceColumns(QVector() << KeyListModelInterface::PrettyName << KeyListModelInterface::PrettyEMail << KeyListModelInterface::Validity << KeyListModelInterface::ValidFrom << KeyListModelInterface::ValidUntil << KeyListModelInterface::TechnicalDetails << KeyListModelInterface::KeyID << KeyListModelInterface::Fingerprint << KeyListModelInterface::OwnerTrust << KeyListModelInterface::Origin << KeyListModelInterface::LastUpdate << KeyListModelInterface::Issuer << KeyListModelInterface::SerialNumber #ifdef GPGME_HAS_REMARKS // If a column is added before this REMARK_COLUMN define has to be updated accordingly << KeyListModelInterface::Remarks #endif ); m_view->setModel(rearangingModel); /* Handle expansion state */ m_expandedKeys = m_group.readEntry("Expanded", QStringList()); connect(m_view, &QTreeView::expanded, this, [this] (const QModelIndex &index) { if (!index.isValid()) { return; } const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value(); const auto fpr = QString::fromLatin1(key.primaryFingerprint()); if (m_expandedKeys.contains(fpr)) { return; } m_expandedKeys << fpr; m_group.writeEntry("Expanded", m_expandedKeys); }); connect(m_view, &QTreeView::collapsed, this, [this] (const QModelIndex &index) { if (!index.isValid()) { return; } const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value(); m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint())); m_group.writeEntry("Expanded", m_expandedKeys); }); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] () { /* We use a single shot timer here to ensure that the keysMayHaveChanged * handlers are all handled before we restore the expand state so that * the model is already populated. */ QTimer::singleShot(0, [this] () { restoreExpandState(); setupRemarkKeys(); if (!m_onceResized) { m_onceResized = true; resizeColumns(); } }); }); resizeColumns(); restoreLayout(); } void KeyTreeView::restoreExpandState() { if (!KeyCache::instance()->initialized()) { qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting."; return; } - for (const auto &fpr: m_expandedKeys) { + for (const auto &fpr: qAsConst(m_expandedKeys)) { const KeyListModelInterface *km = dynamic_cast (m_view->model()); if (!km) { qCWarning(KLEOPATRA_LOG) << "invalid model"; return; } const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData()); if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache"; m_expandedKeys.removeAll(fpr); return; } const auto idx = km->index(key); if (!idx.isValid()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model"; m_expandedKeys.removeAll(fpr); return; } m_view->expand(idx); } } void KeyTreeView::setupRemarkKeys() { #ifdef GPGME_HAS_REMARKS const auto remarkKeys = Remarks::remarkKeys(); if (m_hierarchicalModel) { m_hierarchicalModel->setRemarkKeys(remarkKeys); } if (m_flatModel) { m_flatModel->setRemarkKeys(remarkKeys); } #endif } void KeyTreeView::saveLayout() { QHeaderView *header = m_view->header(); QVariantList columnVisibility; QVariantList columnOrder; QVariantList columnWidths; const int headerCount = header->count(); columnVisibility.reserve(headerCount); columnWidths.reserve(headerCount); columnOrder.reserve(headerCount); for (int i = 0; i < headerCount; ++i) { columnVisibility << QVariant(!m_view->isColumnHidden(i)); columnWidths << QVariant(header->sectionSize(i)); columnOrder << QVariant(header->visualIndex(i)); } m_group.writeEntry("ColumnVisibility", columnVisibility); m_group.writeEntry("ColumnOrder", columnOrder); m_group.writeEntry("ColumnWidths", columnWidths); m_group.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); if (header->isSortIndicatorShown()) { m_group.writeEntry("SortColumn", header->sortIndicatorSection()); } else { m_group.writeEntry("SortColumn", -1); } } void KeyTreeView::restoreLayout() { QHeaderView *header = m_view->header(); QVariantList columnVisibility = m_group.readEntry("ColumnVisibility", QVariantList()); QVariantList columnOrder = m_group.readEntry("ColumnOrder", QVariantList()); QVariantList columnWidths = m_group.readEntry("ColumnWidths", QVariantList()); if (columnVisibility.isEmpty()) { // if config is empty then use default settings // The numbers have to be in line with the order in // setsSourceColumns above m_view->hideColumn(5); for (int i = 7; i < m_view->model()->columnCount(); ++i) { m_view->hideColumn(i); } if (KeyCache::instance()->initialized()) { QTimer::singleShot(0, this, &KeyTreeView::resizeColumns); } } else { for (int i = 0; i < header->count(); ++i) { if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) { // An additional column that was not around last time we saved. // We default to hidden. m_view->hideColumn(i); continue; } bool visible = columnVisibility[i].toBool(); int width = columnWidths[i].toInt(); int order = columnOrder[i].toInt(); header->resizeSection(i, width ? width : 100); header->moveSection(header->visualIndex(i), order); if (i == REMARK_COLUMN) { Remarks::enableRemarks(visible); } if (!visible) { m_view->hideColumn(i); } } m_onceResized = true; } int sortOrder = m_group.readEntry("SortAscending", (int)Qt::AscendingOrder); int sortColumn = m_group.readEntry("SortColumn", -1); if (sortColumn >= 0) { m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); } } KeyTreeView::~KeyTreeView() { saveLayout(); } static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm) { Q_ASSERT(pm); while (QAbstractProxyModel *const sm = qobject_cast(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); } static QItemSelection itemSelectionFromKeys(const std::vector &keys, const KeyListSortFilterProxyModel &proxy) { QItemSelection result; for (const Key &key : keys) { const QModelIndex mi = proxy.index(key); if (mi.isValid()) { result.merge(QItemSelection(mi, mi), QItemSelectionModel::Select); } } return result; } void KeyTreeView::selectKeys(const std::vector &keys) { m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_proxy), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } std::vector KeyTreeView::selectedKeys() const { return m_proxy->keys(m_view->selectionModel()->selectedRows()); } void KeyTreeView::setHierarchicalView(bool on) { if (on == m_isHierarchical) { return; } if (on && !hierarchicalModel()) { qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set"; return; } if (!on && !flatModel()) { qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set"; return; } const std::vector selectedKeys = m_proxy->keys(m_view->selectionModel()->selectedRows()); const Key currentKey = m_proxy->key(m_view->currentIndex()); m_isHierarchical = on; find_last_proxy(m_proxy)->setSourceModel(model()); if (on) { m_view->expandAll(); } selectKeys(selectedKeys); if (!currentKey.isNull()) { const QModelIndex currentIndex = m_proxy->index(currentKey); if (currentIndex.isValid()) { m_view->selectionModel()->setCurrentIndex(m_proxy->index(currentKey), QItemSelectionModel::NoUpdate); m_view->scrollTo(currentIndex); } } Q_EMIT hierarchicalChanged(on); } void KeyTreeView::setKeys(const std::vector &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(KeyListModelInterface::PrettyName, 260); m_view->setColumnWidth(KeyListModelInterface::PrettyEMail, 260); for (int i = 2; i < m_view->model()->columnCount(); ++i) { m_view->resizeColumnToContents(i); } }