diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d2b6780d1..070db3839 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,328 +1,329 @@ add_subdirectory(icons) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (NOT DISABLE_KWATCHGNUPG) add_subdirectory(kwatchgnupg) endif() add_subdirectory(libkleopatraclient) add_subdirectory(conf) add_subdirectory(kconf_update) if(WIN32) set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp) set(_kleopatra_extra_SRCS utils/gnupg-registry.c selftest/registrycheck.cpp) else() set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp) set(_kleopatra_extra_SRCS) endif() set(_kleopatra_uiserver_SRCS uiserver/sessiondata.cpp uiserver/uiserver.cpp ${_kleopatra_extra_uiserver_SRCS} uiserver/assuanserverconnection.cpp uiserver/echocommand.cpp uiserver/decryptverifycommandemailbase.cpp uiserver/decryptverifycommandfilesbase.cpp uiserver/signcommand.cpp uiserver/signencryptfilescommand.cpp uiserver/prepencryptcommand.cpp uiserver/prepsigncommand.cpp uiserver/encryptcommand.cpp uiserver/selectcertificatecommand.cpp uiserver/importfilescommand.cpp uiserver/createchecksumscommand.cpp uiserver/verifychecksumscommand.cpp selftest/uiservercheck.cpp ) if(ASSUAN2_FOUND) include_directories(${ASSUAN2_INCLUDES}) set(_kleopatra_uiserver_extra_libs ${ASSUAN2_LIBRARIES}) else() include_directories(${ASSUAN_INCLUDES}) if(WIN32) set(_kleopatra_uiserver_extra_libs ${ASSUAN_VANILLA_LIBRARIES}) else() set(_kleopatra_uiserver_extra_libs ${ASSUAN_PTHREAD_LIBRARIES}) endif() endif() if(HAVE_GPG_ERR_SOURCE_KLEO) add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) else() add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) endif() ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui) set(_kleopatra_SRCS utils/gnupg-helper.cpp utils/gui-helper.cpp utils/filedialog.cpp utils/kdpipeiodevice.cpp utils/headerview.cpp utils/scrollarea.cpp utils/dragqueen.cpp utils/multivalidator.cpp utils/systemtrayicon.cpp + utils/windowsprocessdevice.cpp utils/hex.cpp utils/path-helper.cpp utils/input.cpp utils/output.cpp utils/validation.cpp utils/wsastarter.cpp utils/iodevicelogger.cpp utils/log.cpp utils/action_data.cpp utils/types.cpp utils/archivedefinition.cpp utils/auditlog.cpp utils/clipboardmenu.cpp utils/kuniqueservice.cpp selftest/selftest.cpp selftest/enginecheck.cpp selftest/gpgconfcheck.cpp selftest/gpgagentcheck.cpp selftest/libkleopatrarccheck.cpp ${_kleopatra_extra_SRCS} view/keylistcontroller.cpp view/keytreeview.cpp view/searchbar.cpp view/smartcardwidget.cpp view/padwidget.cpp view/pgpcardwidget.cpp view/netkeywidget.cpp view/nullpinwidget.cpp view/tabwidget.cpp view/keycacheoverlay.cpp view/waitwidget.cpp view/welcomewidget.cpp dialogs/certificateselectiondialog.cpp dialogs/expirydialog.cpp dialogs/lookupcertificatesdialog.cpp dialogs/ownertrustdialog.cpp dialogs/selftestdialog.cpp dialogs/certifycertificatedialog.cpp dialogs/adduseriddialog.cpp dialogs/exportcertificatesdialog.cpp dialogs/deletecertificatesdialog.cpp dialogs/setinitialpindialog.cpp dialogs/certificatedetailswidget.cpp dialogs/trustchainwidget.cpp dialogs/weboftrustwidget.cpp dialogs/weboftrustdialog.cpp dialogs/exportdialog.cpp dialogs/subkeyswidget.cpp dialogs/gencardkeydialog.cpp dialogs/updatenotification.cpp crypto/controller.cpp crypto/certificateresolver.cpp crypto/sender.cpp crypto/recipient.cpp crypto/task.cpp crypto/taskcollection.cpp crypto/decryptverifytask.cpp crypto/decryptverifyemailcontroller.cpp crypto/decryptverifyfilescontroller.cpp crypto/autodecryptverifyfilescontroller.cpp crypto/encryptemailtask.cpp crypto/encryptemailcontroller.cpp crypto/newsignencryptemailcontroller.cpp crypto/signencrypttask.cpp crypto/signencryptfilescontroller.cpp crypto/signemailtask.cpp crypto/signemailcontroller.cpp crypto/createchecksumscontroller.cpp crypto/verifychecksumscontroller.cpp crypto/gui/wizard.cpp crypto/gui/wizardpage.cpp crypto/gui/certificateselectionline.cpp crypto/gui/certificatelineedit.cpp crypto/gui/signingcertificateselectionwidget.cpp crypto/gui/signingcertificateselectiondialog.cpp crypto/gui/resultitemwidget.cpp crypto/gui/resultlistwidget.cpp crypto/gui/resultpage.cpp crypto/gui/newresultpage.cpp crypto/gui/signencryptfileswizard.cpp crypto/gui/signencryptemailconflictdialog.cpp crypto/gui/decryptverifyoperationwidget.cpp crypto/gui/decryptverifyfileswizard.cpp crypto/gui/decryptverifyfilesdialog.cpp crypto/gui/objectspage.cpp crypto/gui/resolverecipientspage.cpp crypto/gui/signerresolvepage.cpp crypto/gui/encryptemailwizard.cpp crypto/gui/signemailwizard.cpp crypto/gui/signencryptwidget.cpp crypto/gui/signencryptwizard.cpp crypto/gui/unknownrecipientwidget.cpp crypto/gui/verifychecksumsdialog.cpp commands/command.cpp commands/gnupgprocesscommand.cpp commands/detailscommand.cpp commands/exportcertificatecommand.cpp commands/importcertificatescommand.cpp commands/importcertificatefromfilecommand.cpp commands/importcertificatefromclipboardcommand.cpp commands/importcertificatefromdatacommand.cpp commands/lookupcertificatescommand.cpp commands/reloadkeyscommand.cpp commands/refreshx509certscommand.cpp commands/refreshopenpgpcertscommand.cpp commands/deletecertificatescommand.cpp commands/decryptverifyfilescommand.cpp commands/signencryptfilescommand.cpp commands/signencryptfoldercommand.cpp commands/encryptclipboardcommand.cpp commands/signclipboardcommand.cpp commands/decryptverifyclipboardcommand.cpp commands/clearcrlcachecommand.cpp commands/dumpcrlcachecommand.cpp commands/dumpcertificatecommand.cpp commands/importcrlcommand.cpp commands/changeexpirycommand.cpp commands/changeownertrustcommand.cpp commands/changeroottrustcommand.cpp commands/changepassphrasecommand.cpp commands/certifycertificatecommand.cpp commands/selftestcommand.cpp commands/exportsecretkeycommand.cpp commands/exportopenpgpcertstoservercommand.cpp commands/adduseridcommand.cpp commands/newcertificatecommand.cpp commands/setinitialpincommand.cpp commands/learncardkeyscommand.cpp commands/checksumcreatefilescommand.cpp commands/checksumverifyfilescommand.cpp commands/exportpaperkeycommand.cpp commands/importpaperkeycommand.cpp commands/genrevokecommand.cpp commands/keytocardcommand.cpp ${_kleopatra_uiserver_files} conf/configuredialog.cpp newcertificatewizard/listwidget.cpp newcertificatewizard/newcertificatewizard.cpp smartcard/readerstatus.cpp smartcard/card.cpp smartcard/openpgpcard.cpp smartcard/netkeycard.cpp aboutdata.cpp systrayicon.cpp kleopatraapplication.cpp mainwindow.cpp main.cpp ) if(WIN32) configure_file (versioninfo.rc.in versioninfo.rc) set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS}) endif() if(HAVE_KCMUTILS) set (_kleopatra_extra_libs KF5::KCMUtils) else() set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp ${_kleopatra_SRCS}) endif() ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra) if(KLEO_MODEL_TEST) add_definitions(-DKLEO_MODEL_TEST) set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp) endif() ki18n_wrap_ui(_kleopatra_SRCS dialogs/certificationoptionswidget.ui dialogs/expirydialog.ui dialogs/lookupcertificatesdialog.ui dialogs/ownertrustdialog.ui dialogs/selectchecklevelwidget.ui dialogs/selftestdialog.ui dialogs/adduseriddialog.ui dialogs/setinitialpindialog.ui dialogs/certificatedetailswidget.ui dialogs/trustchainwidget.ui dialogs/subkeyswidget.ui newcertificatewizard/listwidget.ui newcertificatewizard/chooseprotocolpage.ui newcertificatewizard/enterdetailspage.ui newcertificatewizard/overviewpage.ui newcertificatewizard/keycreationpage.ui newcertificatewizard/resultpage.ui newcertificatewizard/advancedsettingsdialog.ui ) kconfig_add_kcfg_files(_kleopatra_SRCS kcfg/tooltippreferences.kcfgc kcfg/emailoperationspreferences.kcfgc kcfg/fileoperationspreferences.kcfgc kcfg/smimevalidationpreferences.kcfgc ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png") ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS}) qt5_add_resources(_kleopatra_SRCS kleopatra.qrc) add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS}) set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra) target_link_libraries(kleopatra_bin Gpgmepp QGpgme ${_kleopatra_extra_libs} KF5::Libkleo KF5::Mime KF5::I18n KF5::XmlGui KF5::IconThemes KF5::WindowSystem KF5::CoreAddons KF5::ItemModels KF5::Crash Qt5::Network Qt5::PrintSupport # Printing secret keys ${_kleopatra_uiserver_extra_libs} ${_kleopatra_dbusaddons_libs} kleopatraclientcore ) install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install( PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install( FILES data/kleopatra_signencryptfiles.desktop data/kleopatra_signencryptfolders.desktop data/kleopatra_decryptverifyfiles.desktop data/kleopatra_decryptverifyfolders.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) diff --git a/src/crypto/signencrypttask.cpp b/src/crypto/signencrypttask.cpp index 449d1c342..dc7836167 100644 --- a/src/crypto/signencrypttask.cpp +++ b/src/crypto/signencrypttask.cpp @@ -1,708 +1,708 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencrypttask.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 "signencrypttask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include // for Qt::escape using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { QString formatInputOutputLabel(const QString &input, const QString &output, bool outputDeleted) { return i18nc("Input file --> Output file (rarr is arrow", "%1 → %2", input.toHtmlEscaped(), outputDeleted ? QStringLiteral("%1").arg(output.toHtmlEscaped()) : output.toHtmlEscaped()); } class ErrorResult : public Task::Result { public: ErrorResult(bool sign, bool encrypt, const Error &err, const QString &errStr, const QString &input, const QString &output, const AuditLog &auditLog) : Task::Result(), m_sign(sign), m_encrypt(encrypt), m_error(err), m_errString(errStr), m_inputLabel(input), m_outputLabel(output), m_auditLog(auditLog) {} QString overview() const override; QString details() const override; int errorCode() const override { return m_error.encodedError(); } QString errorString() const override { return m_errString; } VisualCode code() const override { return NeutralError; } AuditLog auditLog() const override { return m_auditLog; } private: const bool m_sign; const bool m_encrypt; const Error m_error; const QString m_errString; const QString m_inputLabel; const QString m_outputLabel; const AuditLog m_auditLog; }; class SignEncryptFilesResult : public Task::Result { public: SignEncryptFilesResult(const SigningResult &sr, const std::shared_ptr &input, const std::shared_ptr &output, bool outputCreated, const AuditLog &auditLog) : Task::Result(), m_sresult(sr), m_inputLabel(input ? input->label() : QString()), m_inputErrorString(input ? input->errorString() : QString()), m_outputLabel(output ? output->label() : QString()), m_outputErrorString(output ? output->errorString() : QString()), m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << endl << "inputError :" << m_inputErrorString << endl << "outputError:" << m_outputErrorString; Q_ASSERT(!m_sresult.isNull()); } SignEncryptFilesResult(const EncryptionResult &er, const std::shared_ptr &input, const std::shared_ptr &output, bool outputCreated, const AuditLog &auditLog) : Task::Result(), m_eresult(er), m_inputLabel(input ? input->label() : QString()), m_inputErrorString(input ? input->errorString() : QString()), m_outputLabel(output ? output->label() : QString()), m_outputErrorString(output ? output->errorString() : QString()), m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << endl << "inputError :" << m_inputErrorString << endl << "outputError:" << m_outputErrorString; Q_ASSERT(!m_eresult.isNull()); } SignEncryptFilesResult(const SigningResult &sr, const EncryptionResult &er, const std::shared_ptr &input, const std::shared_ptr &output, bool outputCreated, const AuditLog &auditLog) : Task::Result(), m_sresult(sr), m_eresult(er), m_inputLabel(input ? input->label() : QString()), m_inputErrorString(input ? input->errorString() : QString()), m_outputLabel(output ? output->label() : QString()), m_outputErrorString(output ? output->errorString() : QString()), m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << endl << "inputError :" << m_inputErrorString << endl << "outputError:" << m_outputErrorString; Q_ASSERT(!m_sresult.isNull() || !m_eresult.isNull()); } QString overview() const override; QString details() const override; int errorCode() const override; QString errorString() const override; VisualCode code() const override; AuditLog auditLog() const override; private: const SigningResult m_sresult; const EncryptionResult m_eresult; const QString m_inputLabel; const QString m_inputErrorString; const QString m_outputLabel; const QString m_outputErrorString; const bool m_outputCreated; const AuditLog m_auditLog; }; static QString makeSigningOverview(const Error &err) { if (err.isCanceled()) { return i18n("Signing canceled."); } if (err) { return i18n("Signing failed."); } return i18n("Signing succeeded."); } static QString makeResultOverview(const SigningResult &result) { return makeSigningOverview(result.error()); } static QString makeEncryptionOverview(const Error &err) { if (err.isCanceled()) { return i18n("Encryption canceled."); } if (err) { return i18n("Encryption failed."); } return i18n("Encryption succeeded."); } static QString makeResultOverview(const EncryptionResult &result) { return makeEncryptionOverview(result.error()); } static QString makeResultOverview(const SigningResult &sr, const EncryptionResult &er) { if (er.isNull() && sr.isNull()) { return QString(); } if (er.isNull()) { return makeResultOverview(sr); } if (sr.isNull()) { return makeResultOverview(er); } if (sr.error().isCanceled() || sr.error()) { return makeResultOverview(sr); } if (er.error().isCanceled() || er.error()) { return makeResultOverview(er); } return i18n("Signing and encryption succeeded."); } static QString escape(QString s) { s = s.toHtmlEscaped(); s.replace(QLatin1Char('\n'), QStringLiteral("
")); return s; } static QString makeResultDetails(const SigningResult &result, const QString &inputError, const QString &outputError) { const Error err = result.error(); if (err.code() == GPG_ERR_EIO) { if (!inputError.isEmpty()) { return i18n("Input error: %1", escape(inputError)); } else if (!outputError.isEmpty()) { return i18n("Output error: %1", escape(outputError)); } } if (err) { return QString::fromLocal8Bit(err.asString()).toHtmlEscaped(); } return QString(); } static QString makeResultDetails(const EncryptionResult &result, const QString &inputError, const QString &outputError) { const Error err = result.error(); if (err.code() == GPG_ERR_EIO) { if (!inputError.isEmpty()) { return i18n("Input error: %1", escape(inputError)); } else if (!outputError.isEmpty()) { return i18n("Output error: %1", escape(outputError)); } } if (err) { return QString::fromLocal8Bit(err.asString()).toHtmlEscaped(); } return i18n(" Encryption succeeded."); } } QString ErrorResult::overview() const { Q_ASSERT(m_error || m_error.isCanceled()); Q_ASSERT(m_sign || m_encrypt); const QString label = formatInputOutputLabel(m_inputLabel, m_outputLabel, true); const bool canceled = m_error.isCanceled(); if (m_sign && m_encrypt) { return canceled ? i18n("%1: Sign/encrypt canceled.", label) : i18n(" %1: Sign/encrypt failed.", label); } return i18nc("label: result. Example: foo -> foo.gpg: Encryption failed.", "%1: %2", label, m_sign ? makeSigningOverview(m_error) : makeEncryptionOverview(m_error)); } QString ErrorResult::details() const { return m_errString; } class SignEncryptTask::Private { friend class ::Kleo::Crypto::SignEncryptTask; SignEncryptTask *const q; public: explicit Private(SignEncryptTask *qq); private: std::unique_ptr createSignJob(GpgME::Protocol proto); std::unique_ptr createSignEncryptJob(GpgME::Protocol proto); std::unique_ptr createEncryptJob(GpgME::Protocol proto); std::shared_ptr makeErrorResult(const Error &err, const QString &errStr, const AuditLog &auditLog); private: void slotResult(const SigningResult &); void slotResult(const SigningResult &, const EncryptionResult &); void slotResult(const EncryptionResult &); private: std::shared_ptr input; std::shared_ptr output; QStringList inputFileNames; QString outputFileName; std::vector signers; std::vector recipients; bool sign : 1; bool encrypt : 1; bool detached : 1; bool symmetric: 1; bool clearsign: 1; QPointer job; std::shared_ptr m_overwritePolicy; }; SignEncryptTask::Private::Private(SignEncryptTask *qq) : q(qq), input(), output(), inputFileNames(), outputFileName(), signers(), recipients(), sign(true), encrypt(true), detached(false), clearsign(false), job(nullptr), m_overwritePolicy(new OverwritePolicy(nullptr)) { q->setAsciiArmor(true); } std::shared_ptr SignEncryptTask::Private::makeErrorResult(const Error &err, const QString &errStr, const AuditLog &auditLog) { return std::shared_ptr(new ErrorResult(sign, encrypt, err, errStr, input->label(), output->label(), auditLog)); } SignEncryptTask::SignEncryptTask(QObject *p) : Task(p), d(new Private(this)) { } SignEncryptTask::~SignEncryptTask() {} void SignEncryptTask::setInputFileName(const QString &fileName) { kleo_assert(!d->job); kleo_assert(!fileName.isEmpty()); d->inputFileNames = QStringList(fileName); } void SignEncryptTask::setInputFileNames(const QStringList &fileNames) { kleo_assert(!d->job); kleo_assert(!fileNames.empty()); d->inputFileNames = fileNames; } void SignEncryptTask::setInput(const std::shared_ptr &input) { kleo_assert(!d->job); kleo_assert(input); d->input = input; } void SignEncryptTask::setOutput(const std::shared_ptr &output) { kleo_assert(!d->job); kleo_assert(output); d->output = output; } void SignEncryptTask::setOutputFileName(const QString &fileName) { kleo_assert(!d->job); kleo_assert(!fileName.isEmpty()); d->outputFileName = fileName; } void SignEncryptTask::setSigners(const std::vector &signers) { kleo_assert(!d->job); d->signers = signers; } void SignEncryptTask::setRecipients(const std::vector &recipients) { kleo_assert(!d->job); d->recipients = recipients; } void SignEncryptTask::setOverwritePolicy(const std::shared_ptr &policy) { kleo_assert(!d->job); d->m_overwritePolicy = policy; } void SignEncryptTask::setSign(bool sign) { kleo_assert(!d->job); d->sign = sign; } void SignEncryptTask::setEncrypt(bool encrypt) { kleo_assert(!d->job); d->encrypt = encrypt; } void SignEncryptTask::setDetachedSignature(bool detached) { kleo_assert(!d->job); d->detached = detached; } void SignEncryptTask::setEncryptSymmetric(bool symmetric) { kleo_assert(!d->job); d->symmetric = symmetric; } void SignEncryptTask::setClearsign(bool clearsign) { kleo_assert(!d->job); d->clearsign = clearsign; } Protocol SignEncryptTask::protocol() const { if (d->sign && !d->signers.empty()) { return d->signers.front().protocol(); } if (d->encrypt || d->symmetric) { if (!d->recipients.empty()) { return d->recipients.front().protocol(); } else { return GpgME::OpenPGP; // symmetric OpenPGP encryption } } throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL), i18n("Cannot determine protocol for task")); } QString SignEncryptTask::label() const { return d->input ? d->input->label() : QString(); } QString SignEncryptTask::tag() const { return Formatting::displayName(protocol()); } unsigned long long SignEncryptTask::inputSize() const { return d->input ? d->input->size() : 0U; } void SignEncryptTask::doStart() { kleo_assert(!d->job); if (d->sign) { kleo_assert(!d->signers.empty()); } kleo_assert(d->input); if (!d->output) { d->output = Output::createFromFile(d->outputFileName, d->m_overwritePolicy); } if (d->encrypt || d->symmetric) { Context::EncryptionFlags flags = Context::AlwaysTrust; if (d->symmetric) { flags = static_cast(flags | Context::Symmetric); qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag"; } if (d->sign) { std::unique_ptr job = d->createSignEncryptJob(protocol()); kleo_assert(job.get()); job->start(d->signers, d->recipients, d->input->ioDevice(), d->output->ioDevice(), flags); d->job = job.release(); } else { std::unique_ptr job = d->createEncryptJob(protocol()); kleo_assert(job.get()); job->start(d->recipients, d->input->ioDevice(), d->output->ioDevice(), flags); d->job = job.release(); } } else if (d->sign) { std::unique_ptr job = d->createSignJob(protocol()); kleo_assert(job.get()); kleo_assert(! (d->detached && d->clearsign)); job->start(d->signers, d->input->ioDevice(), d->output->ioDevice(), d->detached ? GpgME::Detached : d->clearsign ? GpgME::Clearsigned : GpgME::NormalSignatureMode); d->job = job.release(); } else { kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!"); } } void SignEncryptTask::cancel() { if (d->job) { d->job->slotCancel(); } } std::unique_ptr SignEncryptTask::Private::createSignJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signJob(backend->signJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(signJob.get()); connect(signJob.get(), SIGNAL(progress(QString,int,int)), q, SLOT(setProgress(QString,int,int))); connect(signJob.get(), SIGNAL(result(GpgME::SigningResult,QByteArray)), q, SLOT(slotResult(GpgME::SigningResult))); return signJob; } std::unique_ptr SignEncryptTask::Private::createSignEncryptJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signEncryptJob(backend->signEncryptJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(signEncryptJob.get()); connect(signEncryptJob.get(), SIGNAL(progress(QString,int,int)), q, SLOT(setProgress(QString,int,int))); connect(signEncryptJob.get(), SIGNAL(result(GpgME::SigningResult,GpgME::EncryptionResult,QByteArray)), q, SLOT(slotResult(GpgME::SigningResult,GpgME::EncryptionResult))); return signEncryptJob; } std::unique_ptr SignEncryptTask::Private::createEncryptJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr encryptJob(backend->encryptJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(encryptJob.get()); connect(encryptJob.get(), SIGNAL(progress(QString,int,int)), q, SLOT(setProgress(QString,int,int))); connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult,QByteArray)), q, SLOT(slotResult(GpgME::EncryptionResult))); return encryptJob; } void SignEncryptTask::Private::slotResult(const SigningResult &result) { const QGpgME::Job *const job = qobject_cast(q->sender()); const AuditLog auditLog = AuditLog::fromJob(job); bool outputCreated = false; - if (result.error().code()) { - output->cancel(); - } else if (input->failed()) { + if (input->failed()) { q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO), i18n("Input error: %1", escape( input->errorString())), auditLog)); return; + } else if (result.error().code()) { + output->cancel(); } else { try { kleo_assert(!result.isNull()); output->finalize(); outputCreated = true; input->finalize(); } catch (const GpgME::Exception &e) { q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } } q->emitResult(std::shared_ptr(new SignEncryptFilesResult(result, input, output, outputCreated, auditLog))); } void SignEncryptTask::Private::slotResult(const SigningResult &sresult, const EncryptionResult &eresult) { const QGpgME::Job *const job = qobject_cast(q->sender()); const AuditLog auditLog = AuditLog::fromJob(job); bool outputCreated = false; - if (sresult.error().code() || eresult.error().code()) { - output->cancel(); - } else if (input->failed()) { + if (input->failed()) { output->cancel(); q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO), i18n("Input error: %1", escape( input->errorString())), auditLog)); return; + } else if (sresult.error().code() || eresult.error().code()) { + output->cancel(); } else { try { kleo_assert(!sresult.isNull() || !eresult.isNull()); output->finalize(); outputCreated = true; input->finalize(); } catch (const GpgME::Exception &e) { q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } } q->emitResult(std::shared_ptr(new SignEncryptFilesResult(sresult, eresult, input, output, outputCreated, auditLog))); } void SignEncryptTask::Private::slotResult(const EncryptionResult &result) { const QGpgME::Job *const job = qobject_cast(q->sender()); const AuditLog auditLog = AuditLog::fromJob(job); bool outputCreated = false; - if (result.error().code()) { - output->cancel(); - } else if (input->failed()) { + if (input->failed()) { output->cancel(); q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO), i18n("Input error: %1", escape(input->errorString())), auditLog)); return; + } else if (result.error().code()) { + output->cancel(); } else { try { kleo_assert(!result.isNull()); output->finalize(); outputCreated = true; input->finalize(); } catch (const GpgME::Exception &e) { q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } } q->emitResult(std::shared_ptr(new SignEncryptFilesResult(result, input, output, outputCreated, auditLog))); } QString SignEncryptFilesResult::overview() const { const QString files = formatInputOutputLabel(m_inputLabel, m_outputLabel, !m_outputCreated); return files + QLatin1String(": ") + makeOverview(makeResultOverview(m_sresult, m_eresult)); } QString SignEncryptFilesResult::details() const { return errorString(); } int SignEncryptFilesResult::errorCode() const { if (m_sresult.error().code()) { return m_sresult.error().encodedError(); } if (m_eresult.error().code()) { return m_eresult.error().encodedError(); } return 0; } QString SignEncryptFilesResult::errorString() const { const bool sign = !m_sresult.isNull(); const bool encrypt = !m_eresult.isNull(); kleo_assert(sign || encrypt); if (sign && encrypt) { return m_sresult.error().code() ? makeResultDetails(m_sresult, m_inputErrorString, m_outputErrorString) : m_eresult.error().code() ? makeResultDetails(m_eresult, m_inputErrorString, m_outputErrorString) : QString(); } return sign ? makeResultDetails(m_sresult, m_inputErrorString, m_outputErrorString) : /*else*/ makeResultDetails(m_eresult, m_inputErrorString, m_outputErrorString); } Task::Result::VisualCode SignEncryptFilesResult::code() const { if (m_sresult.error().isCanceled() || m_eresult.error().isCanceled()) { return Warning; } return (m_sresult.error().code() || m_eresult.error().code()) ? NeutralError : NeutralSuccess; } AuditLog SignEncryptFilesResult::auditLog() const { return m_auditLog; } #include "moc_signencrypttask.cpp" diff --git a/src/utils/input.cpp b/src/utils/input.cpp index ef14e9996..e1076903e 100644 --- a/src/utils/input.cpp +++ b/src/utils/input.cpp @@ -1,458 +1,483 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/input.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 "input.h" #include "input_p.h" #include "detail_p.h" #include "kdpipeiodevice.h" +#include "windowsprocessdevice.h" #include "log.h" #include "kleo_assert.h" #include "cached.h" #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { class PipeInput : public InputImplBase { public: explicit PipeInput(assuan_fd_t fd); std::shared_ptr ioDevice() const override { return m_io; } unsigned int classification() const override; unsigned long long size() const override { return 0; } private: std::shared_ptr m_io; }; class ProcessStdOutInput : public InputImplBase { public: ~ProcessStdOutInput() { finalize(); } explicit ProcessStdOutInput(const QString &cmd, const QStringList &args, const QDir &wd, const QByteArray &stdin_ = QByteArray()); std::shared_ptr ioDevice() const override { return m_proc; } unsigned int classification() const override { return 0U; // plain text } unsigned long long size() const override { return 0; } QString label() const override; bool failed() const override; private: QString doErrorString() const override; private: const QString m_command; const QStringList m_arguments; - const std::shared_ptr m_proc; +#ifdef Q_OS_WIN + std::shared_ptr m_proc; +#else + std::shared_ptr m_proc; +#endif }; class FileInput : public InputImplBase { public: explicit FileInput(const QString &fileName); explicit FileInput(const std::shared_ptr &file); QString label() const override { return m_io ? QFileInfo(m_fileName).fileName() : InputImplBase::label(); } std::shared_ptr ioDevice() const override { return m_io; } unsigned int classification() const override; unsigned long long size() const override { return QFileInfo(m_fileName).size(); } private: std::shared_ptr m_io; QString m_fileName; }; #ifndef QT_NO_CLIPBOARD class ClipboardInput : public Input { public: explicit ClipboardInput(QClipboard::Mode mode); void setLabel(const QString &label) override; QString label() const override; std::shared_ptr ioDevice() const override { return m_buffer; } unsigned int classification() const override; unsigned long long size() const override { return m_buffer ? m_buffer->buffer().size() : 0; } QString errorString() const override { return QString(); } private: const QClipboard::Mode m_mode; std::shared_ptr m_buffer; }; #endif // QT_NO_CLIPBOARD class ByteArrayInput: public Input { public: explicit ByteArrayInput(QByteArray *data): m_buffer(std::shared_ptr(new QBuffer(data))) { if (!m_buffer->open(QIODevice::ReadOnly)) throw Exception(gpg_error(GPG_ERR_EIO), QStringLiteral("Could not open bytearray for reading?!")); } void setLabel(const QString &label) override { m_label = label; } QString label() const override { return m_label; } std::shared_ptr ioDevice() const override { return m_buffer; } unsigned long long size() const override { return m_buffer ? m_buffer->buffer().size() : 0; } QString errorString() const override { return QString(); } unsigned int classification() const override { return classifyContent(m_buffer->data()); } private: std::shared_ptr m_buffer; QString m_label; }; } std::shared_ptr Input::createFromByteArray(QByteArray *data, const QString &label) { std::shared_ptr po(new ByteArrayInput(data)); po->setLabel(label); return po; } std::shared_ptr Input::createFromPipeDevice(assuan_fd_t fd, const QString &label) { std::shared_ptr po(new PipeInput(fd)); po->setDefaultLabel(label); return po; } PipeInput::PipeInput(assuan_fd_t fd) : InputImplBase(), m_io() { std::shared_ptr kdp(new KDPipeIODevice); errno = 0; if (!kdp->open(fd, QIODevice::ReadOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open FD %1 for reading", _detail::assuanFD2int(fd))); m_io = Log::instance()->createIOLogger(kdp, QStringLiteral("pipe-input"), Log::Read); } unsigned int PipeInput::classification() const { notImplemented(); return 0; } std::shared_ptr Input::createFromFile(const QString &fileName, bool) { return std::shared_ptr(new FileInput(fileName)); } std::shared_ptr Input::createFromFile(const std::shared_ptr &file) { return std::shared_ptr(new FileInput(file)); } FileInput::FileInput(const QString &fileName) : InputImplBase(), m_io(), m_fileName(fileName) { std::shared_ptr file(new QFile(fileName)); errno = 0; if (!file->open(QIODevice::ReadOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open file \"%1\" for reading", fileName)); m_io = Log::instance()->createIOLogger(file, QStringLiteral("file-in"), Log::Read); } FileInput::FileInput(const std::shared_ptr &file) : InputImplBase(), m_io(), m_fileName(file->fileName()) { kleo_assert(file); errno = 0; if (file->isOpen() && !file->isReadable()) throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("File \"%1\" is already open, but not for reading", file->fileName())); if (!file->isOpen() && !file->open(QIODevice::ReadOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open file \"%1\" for reading", m_fileName)); m_io = Log::instance()->createIOLogger(file, QStringLiteral("file-in"), Log::Read); } unsigned int FileInput::classification() const { return classify(m_fileName); } std::shared_ptr Input::createFromProcessStdOut(const QString &command) { return std::shared_ptr(new ProcessStdOutInput(command, QStringList(), QDir::current())); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QStringList &args) { return std::shared_ptr(new ProcessStdOutInput(command, args, QDir::current())); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QStringList &args, const QDir &wd) { return std::shared_ptr(new ProcessStdOutInput(command, args, wd)); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QByteArray &stdin_) { return std::shared_ptr(new ProcessStdOutInput(command, QStringList(), QDir::current(), stdin_)); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QStringList &args, const QByteArray &stdin_) { return std::shared_ptr(new ProcessStdOutInput(command, args, QDir::current(), stdin_)); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QStringList &args, const QDir &wd, const QByteArray &stdin_) { return std::shared_ptr(new ProcessStdOutInput(command, args, wd, stdin_)); } namespace { struct Outputter { const QByteArray &data; explicit Outputter(const QByteArray &data) : data(data) {} }; static QDebug operator<<(QDebug s, const Outputter &o) { if (const quint64 size = o.data.size()) { s << " << (" << size << "bytes)"; } return s; } } ProcessStdOutInput::ProcessStdOutInput(const QString &cmd, const QStringList &args, const QDir &wd, const QByteArray &stdin_) : InputImplBase(), m_command(cmd), - m_arguments(args), - m_proc(new QProcess) + m_arguments(args) { const QIODevice::OpenMode openMode = stdin_.isEmpty() ? QIODevice::ReadOnly : QIODevice::ReadWrite; qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << endl << cmd << args << Outputter(stdin_); if (cmd.isEmpty()) throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Command not specified")); + +#ifndef Q_OS_WIN + m_proc = std::shared_ptr (new QProcess); m_proc->setWorkingDirectory(wd.absolutePath()); m_proc->start(cmd, args, openMode); if (!m_proc->waitForStarted()) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not start %1 process: %2", cmd, m_proc->errorString())); - +#else + m_proc = std::shared_ptr (new WindowsProcessDevice(cmd, args, wd.absolutePath())); + if (!m_proc->open(openMode)) { + throw Exception(gpg_error(GPG_ERR_EIO), + i18n("Could not start %1 process: %2", cmd, m_proc->errorString())); + } +#endif if (!stdin_.isEmpty()) { if (m_proc->write(stdin_) != stdin_.size()) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Failed to write input to %1 process: %2", cmd, m_proc->errorString())); m_proc->closeWriteChannel(); } } QString ProcessStdOutInput::label() const { if (!m_proc) { return InputImplBase::label(); } // output max. 3 arguments const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QLatin1Char(' ')); if (m_arguments.size() > 3) { return i18nc("e.g. \"Output of tar xf - file1 ...\"", "Output of %1 ...", cmdline); } else { return i18nc("e.g. \"Output of tar xf - file\"", "Output of %1", cmdline); } } QString ProcessStdOutInput::doErrorString() const { kleo_assert(m_proc); +#ifdef Q_OS_WIN + const auto err = m_proc->errorString(); + if (!err.isEmpty()) { + return QStringLiteral("%1:\n%2").arg(m_command).arg(err); + } + return QString(); +#else if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) { return QString(); } if (m_proc->error() == QProcess::UnknownError) return i18n("Error while running %1:\n%2", m_command, QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", m_command, m_proc->errorString()); } +#endif } bool ProcessStdOutInput::failed() const { kleo_assert(m_proc); +#ifdef Q_OS_WIN + return !m_proc->errorString().isEmpty(); +#else return !(m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0); +#endif } #ifndef QT_NO_CLIPBOARD std::shared_ptr Input::createFromClipboard() { return std::shared_ptr(new ClipboardInput(QClipboard::Clipboard)); } static QByteArray dataFromClipboard(QClipboard::Mode mode) { Q_UNUSED(mode); if (QClipboard *const cb = QApplication::clipboard()) { return cb->text().toUtf8(); } else { return QByteArray(); } } ClipboardInput::ClipboardInput(QClipboard::Mode mode) : Input(), m_mode(mode), m_buffer(new QBuffer) { m_buffer->setData(dataFromClipboard(mode)); if (!m_buffer->open(QIODevice::ReadOnly)) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not open clipboard for reading")); } void ClipboardInput::setLabel(const QString &) { notImplemented(); } QString ClipboardInput::label() const { switch (m_mode) { case QClipboard::Clipboard: return i18n("Clipboard contents"); case QClipboard::FindBuffer: return i18n("FindBuffer contents"); case QClipboard::Selection: return i18n("Current selection"); }; return QString(); } unsigned int ClipboardInput::classification() const { return classifyContent(m_buffer->data()); } #endif // QT_NO_CLIPBOARD Input::~Input() {} void Input::finalize() { if (const std::shared_ptr io = ioDevice()) if (io->isOpen()) { qCDebug(KLEOPATRA_LOG) << "closing input"; io->close(); } } diff --git a/src/utils/windowsprocessdevice.cpp b/src/utils/windowsprocessdevice.cpp new file mode 100644 index 000000000..b55ecbe76 --- /dev/null +++ b/src/utils/windowsprocessdevice.cpp @@ -0,0 +1,439 @@ +/* utils/windowsprocessdevice.cpp + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2019 g10code 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. + */ + +#ifdef WIN32 +#include "windowsprocessdevice.h" + +#include "kleopatra_debug.h" + +#include "gnupg-helper.h" + +#include + +#include + +/* This is the amount of data GPGME reads at once */ +#define PIPEBUF_SIZE 16384 + +using namespace Kleo; + +class WindowsProcessDevice::Private +{ +public: + ~Private(); + + Private(const QString &path, const QStringList &args, const QString &wd): + mPath(path), + mArgs(args), + mWorkingDirectory(wd), + mStdInRd(nullptr), + mStdInWr(nullptr), + mStdOutRd(nullptr), + mStdOutWr(nullptr), + mStdErrRd(nullptr), + mStdErrWr(nullptr), + mProc(nullptr), + mThread(nullptr), + mEnded(false) + { + + } + + bool start (QIODevice::OpenMode mode); + + qint64 write(const char *data, qint64 size) + { + if (size < 0 || (size >> 32)) { + qCDebug (KLEOPATRA_LOG) << "Invalid write"; + return -1; + } + + if (!mStdInWr) { + qCDebug (KLEOPATRA_LOG) << "Write to closed or read only device"; + return -1; + } + + DWORD dwWritten; + if (!WriteFile(mStdInWr, data, (DWORD) size, &dwWritten, nullptr)) { + qCDebug(KLEOPATRA_LOG) << "Failed to write"; + return -1; + } + if (dwWritten != size) { + qCDebug(KLEOPATRA_LOG) << "Failed to write everything"; + return -1; + } + return size; + } + + qint64 read(char *data, qint64 maxSize) + { + if (!mStdOutRd) { + qCDebug (KLEOPATRA_LOG) << "Read of closed or write only device"; + return -1; + } + + if (!maxSize) { + return 0; + } + + DWORD exitCode = 0; + if (GetExitCodeProcess (mProc, &exitCode)) { + if (exitCode != STILL_ACTIVE) { + if (exitCode) { + qCDebug(KLEOPATRA_LOG) << "Non zero exit code"; + mError = readAllStdErr(); + return -1; + } + mEnded = true; + qCDebug(KLEOPATRA_LOG) << "Process finished with code " << exitCode; + } + } else { + qCDebug(KLEOPATRA_LOG) << "GetExitCodeProcess Failed"; + } + + if (mEnded) { + DWORD avail = 0; + if (!PeekNamedPipe(mStdOutRd, + 0, + 0, + 0, + &avail, + 0)) { + qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; + return -1; + } + if (!avail) { + qCDebug(KLEOPATRA_LOG) << "Process ended and nothing more in pipe"; + return 0; + } + } + + DWORD dwRead; + if (!ReadFile(mStdOutRd, data, (DWORD) maxSize, &dwRead, nullptr)) { + qCDebug(KLEOPATRA_LOG) << "Failed to read"; + return -1; + } + + return dwRead; + } + + QString readAllStdErr() + { + QString ret; + if (!mStdErrRd) { + qCDebug (KLEOPATRA_LOG) << "Read of closed stderr"; + } + DWORD dwRead = 0; + do { + char buf[4096]; + DWORD avail; + if (!PeekNamedPipe(mStdErrRd, + 0, + 0, + 0, + &avail, + 0)) { + qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; + return ret; + } + if (!avail) { + return ret; + } + ReadFile(mStdErrRd, buf, 4096, &dwRead, nullptr); + if (dwRead) { + QByteArray ba (buf, dwRead); + ret += QString::fromLocal8Bit (ba); + } + } while (dwRead); + return ret; + } + + void close() + { + if (mProc) { + TerminateProcess(mProc, 0xf291); + CloseHandle(mProc); + mProc = nullptr; + } + } + + QString errorString() + { + return mError; + } + + void closeWriteChannel() + { + if (mStdInWr) { + CloseHandle(mStdInWr); + mStdInWr = nullptr; + } + } + +private: + QString mPath; + QStringList mArgs; + QString mWorkingDirectory; + QString mError; + HANDLE mStdInRd; + HANDLE mStdInWr; + HANDLE mStdOutRd; + HANDLE mStdOutWr; + HANDLE mStdErrRd; + HANDLE mStdErrWr; + HANDLE mProc; + HANDLE mThread; + bool mEnded; +}; + +WindowsProcessDevice::WindowsProcessDevice(const QString &path, + const QStringList &args, + const QString &wd): + d(new Private(path, args, wd)) +{ + +} + +bool WindowsProcessDevice::open(QIODevice::OpenMode mode) +{ + bool ret = d->start(mode); + if (ret) { + setOpenMode(mode); + } + return ret; +} + +qint64 WindowsProcessDevice::readData(char *data, qint64 maxSize) +{ + return d->read(data, maxSize); +} + +qint64 WindowsProcessDevice::writeData(const char *data, qint64 maxSize) +{ + return d->write(data, maxSize); +} + +bool WindowsProcessDevice::isSequential() const +{ + return true; +} + +void WindowsProcessDevice::closeWriteChannel() +{ + d->closeWriteChannel(); +} + +void WindowsProcessDevice::close() +{ + d->close(); + QIODevice::close(); +} + +QString getLastErrorString() +{ + wchar_t *lpMsgBuf = nullptr; + DWORD dw = GetLastError(); + + FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (wchar_t *) &lpMsgBuf, + 0, NULL); + + QString ret = QString::fromWCharArray(lpMsgBuf); + + LocalFree(lpMsgBuf); + return ret; +} + +WindowsProcessDevice::Private::~Private() +{ + if (mProc) { + close(); + } + if (mThread) { + CloseHandle (mThread); + } + if (mStdInRd) { + CloseHandle (mStdInRd); + } + if (mStdInWr) { + CloseHandle (mStdInRd); + } + if (mStdOutRd) { + CloseHandle (mStdInRd); + } + if (mStdOutWr) { + CloseHandle (mStdInWr); + } + if (mStdErrRd) { + CloseHandle (mStdErrRd); + } + if (mStdErrWr) { + CloseHandle (mStdErrWr); + } +} + +static QString qt_create_commandline(const QString &program, const QStringList &arguments, + const QString &nativeArguments) +{ + QString args; + if (!program.isEmpty()) { + QString programName = program; + if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' '))) + programName = QLatin1Char('\"') + programName + QLatin1Char('\"'); + programName.replace(QLatin1Char('/'), QLatin1Char('\\')); + + // add the prgram as the first arg ... it works better + args = programName + QLatin1Char(' '); + } + + for (int i=0; i 0 && tmp.at(i - 1) == QLatin1Char('\\')) + --i; + tmp.insert(i, QLatin1Char('"')); + tmp.prepend(QLatin1Char('"')); + } + args += QLatin1Char(' ') + tmp; + } + + if (!nativeArguments.isEmpty()) { + if (!args.isEmpty()) + args += QLatin1Char(' '); + args += nativeArguments; + } + + return args; +} + +bool WindowsProcessDevice::Private::start(QIODevice::OpenMode mode) +{ + if (mode != QIODevice::ReadOnly && + mode != QIODevice::WriteOnly && + mode != QIODevice::ReadWrite) { + qCDebug(KLEOPATRA_LOG) << "Unsupported open mode " << mode; + return false; + } + + SECURITY_ATTRIBUTES saAttr; + ZeroMemory (&saAttr, sizeof (SECURITY_ATTRIBUTES)); + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + // Create the pipes + if (!CreatePipe(&mStdOutRd, &mStdOutWr, &saAttr, PIPEBUF_SIZE) || + !CreatePipe(&mStdErrRd, &mStdErrWr, &saAttr, 0) || + !CreatePipe(&mStdInRd, &mStdInWr, &saAttr, PIPEBUF_SIZE)) { + qCDebug(KLEOPATRA_LOG) << "Failed to create pipes"; + mError = getLastErrorString(); + return false; + } + + // Ensure only the proper handles are inherited + if (!SetHandleInformation(mStdOutRd, HANDLE_FLAG_INHERIT, 0) || + !SetHandleInformation(mStdErrRd, HANDLE_FLAG_INHERIT, 0) || + !SetHandleInformation(mStdInWr, HANDLE_FLAG_INHERIT, 0)) { + + qCDebug(KLEOPATRA_LOG) << "Failed to set inherit flag"; + mError = getLastErrorString(); + return false; + } + + PROCESS_INFORMATION piProcInfo; + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + + STARTUPINFO siStartInfo; + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdError = mStdErrWr; + siStartInfo.hStdOutput = mStdOutWr; + siStartInfo.hStdInput = mStdInRd; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + const auto args = qt_create_commandline(mPath, mArgs, QString()); + wchar_t *cmdLine = wcsdup (reinterpret_cast(args.utf16())); + const wchar_t *proc = reinterpret_cast(mPath.utf16()); + const QString nativeWorkingDirectory = QDir::toNativeSeparators(mWorkingDirectory); + const wchar_t *wd = reinterpret_cast(nativeWorkingDirectory.utf16()); + + qCDebug(KLEOPATRA_LOG) << "Spawning:" << args; + // Now lets start + bool suc = CreateProcessW(proc, + cmdLine, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + CREATE_NO_WINDOW,// creation flags + NULL, // use parent's environment + wd, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &piProcInfo); // receives PROCESS_INFORMATION + + free(cmdLine); + if (!suc) { + qCDebug(KLEOPATRA_LOG) << "Failed to create process"; + mError = getLastErrorString(); + return false; + } + + mProc = piProcInfo.hProcess; + mThread = piProcInfo.hThread; + + if (mode == QIODevice::WriteOnly) { + CloseHandle (mStdInRd); + mStdInRd = nullptr; + } + + if (mode == QIODevice::ReadOnly) { + CloseHandle (mStdInWr); + mStdInWr = nullptr; + } + + return true; +} + +QString WindowsProcessDevice::errorString() +{ + return d->errorString(); +} +#endif diff --git a/src/utils/windowsprocessdevice.h b/src/utils/windowsprocessdevice.h new file mode 100644 index 000000000..5e98cf4ca --- /dev/null +++ b/src/utils/windowsprocessdevice.h @@ -0,0 +1,99 @@ +/* utils/windowsprocessdevice.h + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2019 g10code 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. + */ + +#ifdef WIN32 +#ifndef WINDOWSPROCESSDEVICE_H +#define WINDOWSPROCESSDEVICE_H + +#include + +#include +#include + +class QString; +class QStringList; + + +namespace Kleo +{ +/* Simplistic anonymous pipe io device + * + * Create an IODevice using Windows Create Process and pipes + * this class serves as an alternative to use QProcess on + * Windows which event driven nature does not play well + * in our threading and IPC model. + * + * This class was written with gpgtar in mind and mostly + * a reaction to multiple issues we had with QProcess + * and gpgtar in GPGME on Windows. It was so hard to debug them + * that we decided for a simple approach that gives us + * full control. + * + * As there are use cases streaming terrabytes through this + * even the control of the buffer size is an advantage. + * + **/ +class WindowsProcessDevice: public QIODevice +{ +Q_OBJECT +public: + WindowsProcessDevice(const QString &path, const QStringList &args, const QString &wd); + + /* Starts the process. Only supports + QIODevice::ReadOnly + QIODevice::WriteOnly + QIODevice::ReadWrite */ + bool open(OpenMode mode) override; + + /* Terminates the process */ + void close() override; + + bool isSequential() const override; + + /* Closes the write channel */ + void closeWriteChannel(); + + /* Get the an error string either stderr or a windows error */ + QString errorString(); +protected: + /* Blocking read */ + qint64 readData(char* data, qint64 maxSize) override; + /* Blocking write */ + qint64 writeData(const char* data, qint64 size) override; +private: + Q_DISABLE_COPY(WindowsProcessDevice) + class Private; + std::shared_ptr d; +}; + +} // namespace Kleo + +#endif // WINDOWSPROCESSDEVICE_H +#endif // WIN32