Page MenuHome GnuPG

No OneTemporary

diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp
index 7abeca82e..43f85988c 100644
--- a/src/kleopatraapplication.cpp
+++ b/src/kleopatraapplication.cpp
@@ -1,898 +1,898 @@
/*
kleopatraapplication.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "kleopatraapplication.h"
#include "kleopatra_options.h"
#include "mainwindow.h"
#include "settings.h"
#include "smimevalidationpreferences.h"
#include "systrayicon.h"
#include <conf/configuredialog.h>
#include <conf/groupsconfigdialog.h>
#include <dialogs/smartcardwindow.h>
#include <smartcard/readerstatus.h>
#include <Libkleo/GnuPG>
#include <utils/kdpipeiodevice.h>
#include <utils/log.h>
#include <utils/userinfo.h>
#include <gpgme++/key.h>
#include <Libkleo/Classify>
#include <Libkleo/Dn>
#include <Libkleo/FileSystemWatcher>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilterManager>
#include <Libkleo/KeyGroupConfig>
#include <Libkleo/SystemInfo>
#include <uiserver/uiserver.h>
#include "commands/checksumcreatefilescommand.h"
#include "commands/checksumverifyfilescommand.h"
#include "commands/decryptverifyfilescommand.h"
#include "commands/detailscommand.h"
#include "commands/importcertificatefromfilecommand.h"
#include "commands/lookupcertificatescommand.h"
#include "commands/newcertificatesigningrequestcommand.h"
#include "commands/newopenpgpcertificatecommand.h"
#include "commands/signencryptfilescommand.h"
#include "dialogs/updatenotification.h"
#ifdef Q_OS_WIN
#include <utils/winapi-helpers.h>
#endif
#include "kleopatra_debug.h"
#include <KIconLoader>
#include <KLocalizedString>
#include <KMessageBox>
#include <KWindowSystem>
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QFocusFrame>
#include <QProxyStyle>
#if QT_CONFIG(graphicseffect)
#include <QGraphicsEffect>
#endif
#include <QPointer>
#include <QSettings>
#include <QStyleOption>
#include <QStylePainter>
#include <KSharedConfig>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
using namespace Kleo;
using namespace Kleo::Commands;
static void add_resources()
{
KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra"));
KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg"));
}
static QList<QByteArray> default_logging_options()
{
QList<QByteArray> result;
result.push_back("io");
return result;
}
namespace
{
class FocusFrame : public QFocusFrame
{
Q_OBJECT
public:
using QFocusFrame::QFocusFrame;
protected:
void paintEvent(QPaintEvent *event) override;
};
static QRect effectiveWidgetRect(const QWidget *w)
{
// based on QWidgetPrivate::effectiveRectFor
#if QT_CONFIG(graphicseffect)
if (auto graphicsEffect = w->graphicsEffect(); graphicsEffect && graphicsEffect->isEnabled())
return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect();
#endif // QT_CONFIG(graphicseffect)
return w->rect();
}
static QRect clipRect(const QWidget *w)
{
// based on QWidgetPrivate::clipRect
if (!w->isVisible()) {
return QRect();
}
QRect r = effectiveWidgetRect(w);
int ox = 0;
int oy = 0;
while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) {
ox -= w->x();
oy -= w->y();
w = w->parentWidget();
r &= QRect(ox, oy, w->width(), w->height());
}
return r;
}
void FocusFrame::paintEvent(QPaintEvent *)
{
if (!widget()) {
return;
}
QStylePainter p(this);
QStyleOptionFocusRect option;
initStyleOption(&option);
const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option);
const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option);
const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin * 2, vmargin * 2);
p.setClipRect(rect);
p.drawPrimitive(QStyle::PE_FrameFocusRect, option);
}
}
class KleopatraApplication::Private
{
friend class ::KleopatraApplication;
KleopatraApplication *const q;
public:
explicit Private(KleopatraApplication *qq)
: q(qq)
, ignoreNewInstance(true)
, firstNewInstance(true)
#ifndef QT_NO_SYSTEMTRAYICON
, sysTray(nullptr)
#endif
{
}
~Private()
{
#ifndef QT_NO_SYSTEMTRAYICON
delete sysTray;
#endif
}
void setUpSysTrayIcon()
{
#ifndef QT_NO_SYSTEMTRAYICON
Q_ASSERT(readerStatus);
sysTray = new SysTrayIcon();
sysTray->setFirstCardWithNullPin(readerStatus->firstCardWithNullPin());
connect(readerStatus.get(), &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin);
#endif
}
private:
void connectConfigureDialog()
{
if (configureDialog) {
if (q->mainWindow()) {
connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted()));
}
connect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged);
}
}
void disconnectConfigureDialog()
{
if (configureDialog) {
if (q->mainWindow()) {
disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted()));
}
disconnect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged);
}
}
public:
bool ignoreNewInstance;
bool firstNewInstance;
QPointer<FocusFrame> focusFrame;
QPointer<ConfigureDialog> configureDialog;
QPointer<GroupsConfigDialog> groupsConfigDialog;
QPointer<MainWindow> mainWindow;
QPointer<SmartCardWindow> smartCardWindow;
std::unique_ptr<SmartCard::ReaderStatus> readerStatus;
#ifndef QT_NO_SYSTEMTRAYICON
SysTrayIcon *sysTray;
#endif
std::shared_ptr<KeyGroupConfig> groupConfig;
std::shared_ptr<KeyCache> keyCache;
std::shared_ptr<Log> log;
std::shared_ptr<FileSystemWatcher> watcher;
std::shared_ptr<QSettings> distroSettings;
public:
void setupKeyCache()
{
keyCache = KeyCache::mutableInstance();
keyCache->setRefreshInterval(SMimeValidationPreferences{}.refreshInterval());
watcher.reset(new FileSystemWatcher);
watcher->whitelistFiles(gnupgFileWhitelist());
watcher->addPaths(gnupgFolderWhitelist());
watcher->setDelay(1000);
keyCache->addFileSystemWatcher(watcher);
keyCache->setGroupConfig(groupConfig);
keyCache->setGroupsEnabled(Settings().groupsEnabled());
// always enable remarks (aka tags); in particular, this triggers a
// relisting of the keys with signatures and signature notations
// after the initial (fast) key listing
keyCache->enableRemarks(true);
}
void setUpFilterManager()
{
if (!Settings{}.cmsEnabled()) {
KeyFilterManager::instance()->alwaysFilterByProtocol(GpgME::OpenPGP);
}
}
void setupLogging()
{
log = Log::mutableInstance();
const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS");
const bool logAll = envOptions.trimmed() == "all";
const QList<QByteArray> options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(',');
const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR");
if (dirNative.isEmpty()) {
return;
}
const QString dir = QFile::decodeName(dirNative);
const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid()));
std::unique_ptr<QFile> logFile(new QFile(logFileName));
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) {
qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled";
return;
}
log->setOutputDirectory(dir);
if (logAll || options.contains("io")) {
log->setIOLoggingEnabled(true);
}
qInstallMessageHandler(Log::messageHandler);
if (logAll || options.contains("pipeio")) {
KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug);
}
UiServer::setLogStream(log->logFile());
}
void updateFocusFrame(QWidget *focusWidget)
{
if (focusWidget && focusWidget->inherits("QLabel") && focusWidget->window()->testAttribute(Qt::WA_KeyboardFocusChange)) {
if (!focusFrame) {
focusFrame = new FocusFrame{focusWidget};
}
focusFrame->setWidget(focusWidget);
} else if (focusFrame) {
focusFrame->setWidget(nullptr);
}
}
MainWindow *getOrCreateMainWindow()
{
auto mw = q->mainWindow();
if (!mw) {
mw = new MainWindow;
mw->setAttribute(Qt::WA_DeleteOnClose);
q->setMainWindow(mw);
}
return mw;
}
};
class KleopatraProxyStyle : public QProxyStyle
{
public:
int styleHint(StyleHint hint, const QStyleOption *option = nullptr, const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override
{
// disable parent<->child navigation in tree views with left/right arrow keys
// because this interferes with column by column navigation that is required
// for accessibility
if (hint == QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren)
return 0;
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
};
KleopatraApplication::KleopatraApplication(int &argc, char *argv[])
: QApplication(argc, argv)
, d(new Private(this))
{
setStyle(new KleopatraProxyStyle);
#ifdef Q_OS_WIN
if (SystemInfo::isHighContrastModeActive()) {
// use colors specified by Windows if high-contrast mode is active
QPalette highContrastPalette = palette();
const QColor linkColor = win_getSysColor(COLOR_HOTLIGHT);
highContrastPalette.setColor(QPalette::All, QPalette::Link, linkColor);
highContrastPalette.setColor(QPalette::All, QPalette::LinkVisited, linkColor);
const QColor disabledColor = win_getSysColor(COLOR_GRAYTEXT);
highContrastPalette.setColor(QPalette::Disabled, QPalette::WindowText, disabledColor);
highContrastPalette.setColor(QPalette::Disabled, QPalette::Text, disabledColor);
highContrastPalette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor);
highContrastPalette.setColor(QPalette::All, QPalette::PlaceholderText, disabledColor);
setPalette(highContrastPalette);
}
#endif
connect(this, &QApplication::focusChanged, this, [this](QWidget *, QWidget *now) {
d->updateFocusFrame(now);
});
}
void KleopatraApplication::init()
{
const QString groupConfigPath = Kleo::gnupgHomeDirectory() + QStringLiteral("/kleopatra/kleopatragroupsrc");
d->groupConfig = std::make_shared<KeyGroupConfig>(groupConfigPath);
const auto blockedUrlSchemes = Settings{}.blockedUrlSchemes();
for (const auto &scheme : blockedUrlSchemes) {
QDesktopServices::setUrlHandler(scheme, this, "blockUrl");
}
add_resources();
DN::setAttributeOrder(Settings{}.attributeOrder());
/* Start the gpg-agent early, this is done explicitly
* because on an empty keyring our keylistings wont start
* the agent. In that case any assuan-connect calls to
* the agent will fail. The requested start via the
* connection is additionally done in case the gpg-agent
* is killed while Kleopatra is running. */
startGpgAgent();
d->readerStatus.reset(new SmartCard::ReaderStatus);
connect(d->readerStatus.get(), &SmartCard::ReaderStatus::startOfGpgAgentRequested, this, &KleopatraApplication::startGpgAgent);
d->setupKeyCache();
d->setUpSysTrayIcon();
d->setUpFilterManager();
d->setupLogging();
#ifndef QT_NO_SYSTEMTRAYICON
if (d->sysTray) {
d->sysTray->show();
}
#endif
if (!Kleo::userIsElevated()) {
// For users running Kleo with elevated permissions on Windows we
// always quit the application when the last window is closed.
setQuitOnLastWindowClosed(false);
}
// Sync config when we are about to quit
connect(this, &QApplication::aboutToQuit, this, []() {
KSharedConfig::openConfig()->sync();
});
}
KleopatraApplication::~KleopatraApplication()
{
delete d->groupsConfigDialog;
delete d->smartCardWindow;
delete d->mainWindow;
}
namespace
{
using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol);
}
void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory)
{
QCommandLineParser parser;
kleopatra_options(&parser);
QString err;
if (!arguments.isEmpty() && !parser.parse(arguments)) {
err = parser.errorText();
} else if (arguments.isEmpty()) {
// KDBusServices omits the application name if no other
// arguments are provided. In that case the parser prints
// a warning.
parser.parse(QStringList() << QCoreApplication::applicationFilePath());
}
if (err.isEmpty()) {
err = newInstance(parser, workingDirectory);
}
if (!err.isEmpty()) {
KMessageBox::error(nullptr, err.toHtmlEscaped(), i18nc("@title:window", "Failed to execute command"));
Q_EMIT setExitValue(1);
return;
}
Q_EMIT setExitValue(0);
}
QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory)
{
if (d->ignoreNewInstance) {
qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance";
return QString();
}
QStringList files;
const QDir cwd = QDir(workingDirectory);
bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search"));
// Query and Search treat positional arguments differently, see below.
if (!queryMode) {
const auto positionalArguments = parser.positionalArguments();
for (const QString &file : positionalArguments) {
// We do not check that file exists here. Better handle
// these errors in the UI.
if (QFileInfo(file).isAbsolute()) {
files << file;
} else {
files << cwd.absoluteFilePath(file);
}
}
}
GpgME::Protocol protocol = GpgME::UnknownProtocol;
if (parser.isSet(QStringLiteral("openpgp"))) {
qCDebug(KLEOPATRA_LOG) << "found OpenPGP";
protocol = GpgME::OpenPGP;
}
if (parser.isSet(QStringLiteral("cms"))) {
qCDebug(KLEOPATRA_LOG) << "found CMS";
if (protocol == GpgME::OpenPGP) {
return i18n("Ambiguous protocol: --openpgp and --cms");
}
protocol = GpgME::CMS;
}
// Check for Parent Window id
WId parentId = 0;
if (parser.isSet(QStringLiteral("parent-windowid"))) {
#ifdef Q_OS_WIN
// WId is not a portable type as it is a pointer type on Windows.
// casting it from an integer is ok though as the values are guaranteed to
// be compatible in the documentation.
- parentId = reinterpret_cast<WId>(parser.value(QStringLiteral("parent-windowid")).toUInt());
+ parentId = static_cast<WId>((parser.value(QStringLiteral("parent-windowid")).toUInt()));
#else
parentId = parser.value(QStringLiteral("parent-windowid")).toUInt();
#endif
}
// Handle openpgp4fpr URI scheme
QString needle;
if (queryMode) {
needle = parser.positionalArguments().join(QLatin1Char(' '));
}
if (needle.startsWith(QLatin1StringView("openpgp4fpr:"))) {
needle.remove(0, 12);
}
// Check for --search command.
if (parser.isSet(QStringLiteral("search"))) {
// This is an extra command instead of a combination with the
// similar query to avoid changing the older query commands behavior
// and query's "show details if a certificate exist or search on a
// keyserver" logic is hard to explain and use consistently.
if (needle.isEmpty()) {
return i18n("No search string specified for --search");
}
auto const cmd = new LookupCertificatesCommand(needle, nullptr);
cmd->setParentWId(parentId);
cmd->start();
return QString();
}
// Check for --query command
if (parser.isSet(QStringLiteral("query"))) {
if (needle.isEmpty()) {
return i18n("No fingerprint argument specified for --query");
}
auto cmd = Command::commandForQuery(needle);
cmd->setParentWId(parentId);
cmd->start();
return QString();
}
// Check for --gen-key command
if (parser.isSet(QStringLiteral("gen-key"))) {
if (protocol == GpgME::CMS) {
const Kleo::Settings settings{};
if (settings.cmsEnabled() && settings.cmsCertificateCreationAllowed()) {
auto cmd = new NewCertificateSigningRequestCommand;
cmd->setParentWId(parentId);
cmd->start();
} else {
return i18n("You are not allowed to create S/MIME certificate signing requests.");
}
} else {
auto cmd = new NewOpenPGPCertificateCommand;
cmd->setParentWId(parentId);
cmd->start();
}
return QString();
}
// Check for --config command
if (parser.isSet(QStringLiteral("config"))) {
openConfigDialogWithForeignParent(parentId);
return QString();
}
struct FuncInfo {
QString optionName;
Func func;
};
// While most of these options can be handled by the content autodetection
// below it might be useful to override the autodetection if the input is in
// doubt and you e.g. only want to import .asc files or fail and not decrypt them
// if they are actually encrypted data.
static const std::vector<FuncInfo> funcMap{
{QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile},
{QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles},
{QStringLiteral("sign"), &KleopatraApplication::signFiles},
{QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles},
{QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles},
{QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles},
{QStringLiteral("verify"), &KleopatraApplication::verifyFiles},
{QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles},
{QStringLiteral("checksum"), &KleopatraApplication::checksumFiles},
};
QString found;
Func foundFunc = nullptr;
for (const auto &[opt, fn] : funcMap) {
if (parser.isSet(opt) && found.isEmpty()) {
found = opt;
foundFunc = fn;
} else if (parser.isSet(opt)) {
return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt);
}
}
QStringList errors;
if (!found.isEmpty()) {
if (files.empty()) {
return i18n("No files specified for \"%1\" command", found);
}
qCDebug(KLEOPATRA_LOG) << "found" << found;
(this->*foundFunc)(files, protocol);
} else {
if (files.empty()) {
if (!(d->firstNewInstance && isSessionRestored())) {
qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow";
openOrRaiseMainWindow();
}
} else {
for (const QString &fileName : std::as_const(files)) {
QFileInfo fi(fileName);
if (!fi.isReadable()) {
errors << i18n("Cannot read \"%1\"", fileName);
}
}
handleFiles(files, parentId);
}
}
d->firstNewInstance = false;
#ifdef Q_OS_WIN
// On Windows we might be started from the
// explorer in any working directory. E.g.
// a double click on a file. To avoid preventing
// the folder from deletion we set the
// working directory to the users homedir.
QDir::setCurrent(QDir::homePath());
#endif
return errors.join(QLatin1Char('\n'));
}
void KleopatraApplication::handleFiles(const QStringList &files, WId parentId)
{
auto mw = d->getOrCreateMainWindow();
const QList<Command *> allCmds = Command::commandsForFiles(files, mw->keyListController());
for (Command *cmd : allCmds) {
if (parentId) {
cmd->setParentWId(parentId);
} else {
cmd->setParentWidget(mw);
}
if (dynamic_cast<ImportCertificateFromFileCommand *>(cmd)) {
openOrRaiseMainWindow();
}
cmd->start();
}
}
const MainWindow *KleopatraApplication::mainWindow() const
{
return d->mainWindow;
}
MainWindow *KleopatraApplication::mainWindow()
{
return d->mainWindow;
}
void KleopatraApplication::setMainWindow(MainWindow *mainWindow)
{
if (mainWindow == d->mainWindow) {
return;
}
d->disconnectConfigureDialog();
d->mainWindow = mainWindow;
#ifndef QT_NO_SYSTEMTRAYICON
if (d->sysTray) {
d->sysTray->setMainWindow(mainWindow);
}
#endif
d->connectConfigureDialog();
}
static void open_or_raise(QWidget *w)
{
#ifdef Q_OS_WIN
if (w->isMinimized()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "unminimizing and raising window";
w->raise();
} else if (w->isVisible()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "raising window";
w->raise();
#else
if (w->isVisible()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "activating window";
KWindowSystem::updateStartupId(w->windowHandle());
KWindowSystem::activateWindow(w->windowHandle());
#endif
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "showing window";
w->show();
}
}
void KleopatraApplication::toggleMainWindowVisibility()
{
if (mainWindow()) {
mainWindow()->setVisible(!mainWindow()->isVisible());
} else {
openOrRaiseMainWindow();
}
if (mainWindow()->isVisible()) {
mainWindow()->exportWindow();
} else {
mainWindow()->unexportWindow();
}
}
void KleopatraApplication::restoreMainWindow()
{
qCDebug(KLEOPATRA_LOG) << "restoring main window";
// Sanity checks
if (!isSessionRestored()) {
qCDebug(KLEOPATRA_LOG) << "Not in session restore";
return;
}
if (mainWindow()) {
qCDebug(KLEOPATRA_LOG) << "Already have main window";
return;
}
auto mw = d->getOrCreateMainWindow();
if (KMainWindow::canBeRestored(1)) {
// restore to hidden state, Mainwindow::readProperties() will
// restore saved visibility.
mw->restore(1, false);
}
}
void KleopatraApplication::openOrRaiseMainWindow()
{
auto mw = d->getOrCreateMainWindow();
open_or_raise(mw);
UpdateNotification::checkUpdate(mw);
}
void KleopatraApplication::openOrRaiseSmartCardWindow()
{
if (!d->smartCardWindow) {
d->smartCardWindow = new SmartCardWindow;
d->smartCardWindow->setAttribute(Qt::WA_DeleteOnClose);
}
open_or_raise(d->smartCardWindow);
}
void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId)
{
if (!d->configureDialog) {
d->configureDialog = new ConfigureDialog;
d->configureDialog->setAttribute(Qt::WA_DeleteOnClose);
d->connectConfigureDialog();
}
// This is similar to what the commands do.
if (parentWId) {
if (QWidget *pw = QWidget::find(parentWId)) {
d->configureDialog->setParent(pw, d->configureDialog->windowFlags());
} else {
d->configureDialog->setAttribute(Qt::WA_NativeWindow, true);
KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId);
}
}
open_or_raise(d->configureDialog);
// If we have a parent we want to raise over it.
if (parentWId) {
d->configureDialog->raise();
}
}
void KleopatraApplication::openOrRaiseConfigDialog()
{
openConfigDialogWithForeignParent(0);
}
void KleopatraApplication::openOrRaiseGroupsConfigDialog(QWidget *parent)
{
if (!d->groupsConfigDialog) {
d->groupsConfigDialog = new GroupsConfigDialog{parent};
d->groupsConfigDialog->setAttribute(Qt::WA_DeleteOnClose);
} else {
// reparent the dialog to ensure it's shown on top of the (modal) parent
d->groupsConfigDialog->setParent(parent, Qt::Dialog);
}
open_or_raise(d->groupsConfigDialog);
}
#ifndef QT_NO_SYSTEMTRAYICON
void KleopatraApplication::startMonitoringSmartCard()
{
Q_ASSERT(d->readerStatus);
d->readerStatus->startMonitoring();
}
#endif // QT_NO_SYSTEMTRAYICON
void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/)
{
openOrRaiseMainWindow();
if (!files.empty()) {
mainWindow()->importCertificatesFromFile(files);
}
}
void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto)
{
auto const cmd = new SignEncryptFilesCommand(files, nullptr);
cmd->setEncryptionPolicy(Force);
cmd->setSigningPolicy(Allow);
if (proto != GpgME::UnknownProtocol) {
cmd->setProtocol(proto);
}
cmd->start();
}
void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto)
{
auto const cmd = new SignEncryptFilesCommand(files, nullptr);
cmd->setSigningPolicy(Force);
cmd->setEncryptionPolicy(Deny);
if (proto != GpgME::UnknownProtocol) {
cmd->setProtocol(proto);
}
cmd->start();
}
void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto)
{
auto const cmd = new SignEncryptFilesCommand(files, nullptr);
if (proto != GpgME::UnknownProtocol) {
cmd->setProtocol(proto);
}
cmd->start();
}
void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
cmd->setOperation(Decrypt);
cmd->start();
}
void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
cmd->setOperation(Verify);
cmd->start();
}
void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
auto const cmd = new DecryptVerifyFilesCommand(files, nullptr);
cmd->start();
}
void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/)
{
QStringList verifyFiles, createFiles;
for (const QString &file : files) {
if (isChecksumFile(file)) {
verifyFiles << file;
} else {
createFiles << file;
}
}
if (!verifyFiles.isEmpty()) {
auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr);
cmd->start();
}
if (!createFiles.isEmpty()) {
auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr);
cmd->start();
}
}
void KleopatraApplication::setIgnoreNewInstance(bool ignore)
{
d->ignoreNewInstance = ignore;
}
bool KleopatraApplication::ignoreNewInstance() const
{
return d->ignoreNewInstance;
}
void KleopatraApplication::blockUrl(const QUrl &url)
{
qCDebug(KLEOPATRA_LOG) << "Blocking URL" << url;
KMessageBox::error(mainWindow(), i18n("Opening an external link is administratively prohibited."), i18nc("@title:window", "Prohibited"));
}
void KleopatraApplication::startGpgAgent()
{
Kleo::launchGpgAgent();
}
void KleopatraApplication::setDistributionSettings(const std::shared_ptr<QSettings> &settings)
{
d->distroSettings = settings;
}
std::shared_ptr<QSettings> KleopatraApplication::distributionSettings() const
{
return d->distroSettings;
}
#include "kleopatraapplication.moc"
#include "moc_kleopatraapplication.cpp"
diff --git a/src/uiserver/assuanserverconnection.cpp b/src/uiserver/assuanserverconnection.cpp
index 4852566de..60fccc197 100644
--- a/src/uiserver/assuanserverconnection.cpp
+++ b/src/uiserver/assuanserverconnection.cpp
@@ -1,1493 +1,1493 @@
/* -*- mode: c++; c-basic-offset:4 -*-
uiserver/assuanserverconnection.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef QT_NO_CAST_TO_ASCII
#define QT_NO_CAST_TO_ASCII
#endif
#ifndef QT_NO_CAST_FROM_ASCII
#define QT_NO_CAST_FROM_ASCII
#endif
#include <config-kleopatra.h>
#include <version-kleopatra.h>
#include "assuancommand.h"
#include "assuanserverconnection.h"
#include "sessiondata.h"
#include <utils/detail_p.h>
#include <utils/input.h>
#include <utils/kleo_assert.h>
#include <utils/log.h>
#include <utils/output.h>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/Hex>
#include <Libkleo/KeyCache>
#include <Libkleo/KleoException>
#include <Libkleo/Stl_Util>
#include <gpgme++/data.h>
#include <gpgme++/key.h>
#include <KMime/HeaderParsing>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <KWindowSystem>
#include <QCoreApplication>
#include <QFileInfo>
#include <QPointer>
#include <QRegularExpression>
#include <QSocketNotifier>
#include <QStringList>
#include <QTimer>
#include <QVariant>
#include <QWidget>
#include <algorithm>
#include <functional>
#include <map>
#include <type_traits>
#include <cerrno>
#ifdef __GLIBCXX__
#include <ext/algorithm> // for is_sorted
#endif
#ifdef Q_OS_WIN
#include <io.h>
#include <process.h>
#else
#include <sys/types.h>
#include <unistd.h>
#endif
using namespace Kleo;
static const unsigned int INIT_SOCKET_FLAGS = 3; // says info assuan...
// static int(*USE_DEFAULT_HANDLER)(assuan_context_t,char*) = 0;
static const int FOR_READING = 0;
static const unsigned int MAX_ACTIVE_FDS = 32;
static void my_assuan_release(assuan_context_t ctx)
{
if (ctx) {
assuan_release(ctx);
}
}
// std::shared_ptr for assuan_context_t w/ deleter enforced to assuan_deinit_server:
using AssuanContextBase = std::shared_ptr<std::remove_pointer<assuan_context_t>::type>;
struct AssuanContext : AssuanContextBase {
AssuanContext()
: AssuanContextBase()
{
}
explicit AssuanContext(assuan_context_t ctx)
: AssuanContextBase(ctx, &my_assuan_release)
{
}
void reset(assuan_context_t ctx = nullptr)
{
AssuanContextBase::reset(ctx, &my_assuan_release);
}
};
static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const char *err_msg)
{
return assuan_process_done(ctx, assuan_set_error(ctx, err, err_msg));
}
static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const QString &err_msg)
{
return assuan_process_done_msg(ctx, err, err_msg.toUtf8().constData());
}
static std::map<std::string, std::string> upcase_option(const char *option, std::map<std::string, std::string> options)
{
std::string value;
bool value_found = false;
auto it = options.begin();
while (it != options.end())
if (qstricmp(it->first.c_str(), option) == 0) {
value = it->second;
options.erase(it++);
value_found = true;
} else {
++it;
}
if (value_found) {
options[option] = value;
}
return options;
}
static std::map<std::string, std::string> parse_commandline(const char *line)
{
std::map<std::string, std::string> result;
if (line) {
const char *begin = line;
const char *lastEQ = nullptr;
while (*line) {
if (*line == ' ' || *line == '\t') {
if (begin != line) {
if (begin[0] == '-' && begin[1] == '-') {
begin += 2; // skip initial "--"
}
if (lastEQ && lastEQ > begin) {
result[std::string(begin, lastEQ - begin)] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1)));
} else {
result[std::string(begin, line - begin)] = std::string();
}
}
begin = line + 1;
} else if (*line == '=') {
if (line == begin)
throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("No option name given"));
else {
lastEQ = line;
}
}
++line;
}
if (begin != line) {
if (begin[0] == '-' && begin[1] == '-') {
begin += 2; // skip initial "--"
}
if (lastEQ && lastEQ > begin) {
result[std::string(begin, lastEQ - begin)] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1)));
} else {
result[begin] = std::string();
}
}
}
return result;
}
static WId wid_from_string(const QString &winIdStr, bool *ok = nullptr)
{
return static_cast<WId>(winIdStr.toULongLong(ok, 16));
}
static void apply_window_id(QWidget *widget, const QString &winIdStr)
{
if (!widget || winIdStr.isEmpty()) {
return;
}
bool ok = false;
const WId wid = wid_from_string(winIdStr, &ok);
if (!ok) {
qCDebug(KLEOPATRA_LOG) << "window-id value" << wid << "doesn't look like a number";
return;
}
if (QWidget *pw = QWidget::find(wid)) {
widget->setParent(pw, widget->windowFlags());
} else {
widget->setAttribute(Qt::WA_NativeWindow, true);
KWindowSystem::setMainWindow(widget->windowHandle(), wid);
}
}
//
//
// AssuanServerConnection:
//
//
class AssuanServerConnection::Private : public QObject
{
Q_OBJECT
friend class ::Kleo::AssuanServerConnection;
friend class ::Kleo::AssuanCommandFactory;
friend class ::Kleo::AssuanCommand;
AssuanServerConnection *const q;
public:
Private(assuan_fd_t fd_, const std::vector<std::shared_ptr<AssuanCommandFactory>> &factories_, AssuanServerConnection *qq);
~Private() override;
Q_SIGNALS:
void startKeyManager();
public Q_SLOTS:
- void slotReadActivity(int)
+ void slotReadActivity(intptr_t)
{
Q_ASSERT(ctx);
int done = false;
if (const int err = assuan_process_next(ctx.get(), &done) || done) {
// if ( err == -1 || gpg_err_code(err) == GPG_ERR_EOF ) {
topHalfDeletion();
if (nohupedCommands.empty()) {
bottomHalfDeletion();
}
//} else {
// assuan_process_done( ctx.get(), err );
// return;
//}
}
}
int startCommandBottomHalf();
private:
void nohupDone(AssuanCommand *cmd)
{
const auto it = std::find_if(nohupedCommands.begin(), nohupedCommands.end(), [cmd](const std::shared_ptr<AssuanCommand> &other) {
return other.get() == cmd;
});
Q_ASSERT(it != nohupedCommands.end());
nohupedCommands.erase(it);
if (nohupedCommands.empty() && closed) {
bottomHalfDeletion();
}
}
void commandDone(AssuanCommand *cmd)
{
if (!cmd || cmd != currentCommand.get()) {
return;
}
currentCommand.reset();
}
void topHalfDeletion()
{
if (currentCommand) {
currentCommand->canceled();
}
if (fd != ASSUAN_INVALID_FD) {
#if defined(Q_OS_WIN)
CloseHandle(fd);
#else
::close(fd);
#endif
}
notifiers.clear();
closed = true;
}
void bottomHalfDeletion()
{
if (sessionId) {
SessionDataHandler::instance()->exitSession(sessionId);
}
cleanup();
const QPointer<Private> that = this;
Q_EMIT q->closed(q);
if (that) { // still there
q->deleteLater();
}
}
private:
static gpg_error_t reset_handler(assuan_context_t ctx_, char *)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
conn.reset();
return 0;
}
static gpg_error_t option_handler(assuan_context_t ctx_, const char *key, const char *value)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
if (key && key[0] == '-' && key[1] == '-') {
key += 2; // skip "--"
}
conn.options[key] = QString::fromUtf8(value);
return 0;
// return gpg_error( GPG_ERR_UNKNOWN_OPTION );
}
static gpg_error_t session_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
const QString str = QString::fromUtf8(line);
static const QRegularExpression rx(QRegularExpression::anchoredPattern(uR"((\d+)(?:\s+(.*))?)"));
const QRegularExpressionMatch match = rx.match(str);
if (!match.hasMatch()) {
static const QString errorString = i18n("Parse error");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString);
}
bool ok = false;
if (const qulonglong id = match.captured(1).toULongLong(&ok)) {
if (ok && id <= std::numeric_limits<unsigned int>::max()) {
SessionDataHandler::instance()->enterSession(id);
conn.sessionId = id;
} else {
static const QString errorString = i18n("Parse error: numeric session id too large");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString);
}
}
const QString cap2 = match.captured(2);
if (!cap2.isEmpty()) {
conn.sessionTitle = cap2;
}
qCDebug(KLEOPATRA_LOG) << "session_handler: "
<< "id=" << static_cast<unsigned long>(conn.sessionId) << ", title=" << qPrintable(conn.sessionTitle);
return assuan_process_done(ctx_, 0);
}
static gpg_error_t capabilities_handler(assuan_context_t ctx_, char *line)
{
if (!QByteArray(line).trimmed().isEmpty()) {
static const QString errorString = i18n("CAPABILITIES does not take arguments");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
}
static const char capabilities[] =
"SENDER=info\n"
"RECIPIENT=info\n"
"SESSION\n";
return assuan_process_done(ctx_, assuan_send_data(ctx_, capabilities, sizeof capabilities - 1));
}
static gpg_error_t getinfo_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
if (qstrcmp(line, "version") == 0) {
static const char version[] = "Kleopatra " KLEOPATRA_VERSION_STRING;
return assuan_process_done(ctx_, assuan_send_data(ctx_, version, sizeof version - 1));
}
QByteArray ba;
if (qstrcmp(line, "pid") == 0) {
ba = QByteArray::number(QCoreApplication::applicationPid());
} else if (qstrcmp(line, "options") == 0) {
ba = conn.dumpOptions();
} else if (qstrcmp(line, "x-mementos") == 0) {
ba = conn.dumpMementos();
} else if (qstrcmp(line, "senders") == 0) {
ba = conn.dumpSenders();
} else if (qstrcmp(line, "recipients") == 0) {
ba = conn.dumpRecipients();
} else if (qstrcmp(line, "x-files") == 0) {
ba = conn.dumpFiles();
} else {
static const QString errorString = i18n("Unknown value for WHAT");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
}
return assuan_process_done(ctx_, assuan_send_data(ctx_, ba.constData(), ba.size()));
}
static gpg_error_t start_keymanager_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
if (line && *line) {
static const QString errorString = i18n("START_KEYMANAGER does not take arguments");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
}
Q_EMIT conn.q->startKeyManagerRequested();
return assuan_process_done(ctx_, 0);
}
static gpg_error_t start_confdialog_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
if (line && *line) {
static const QString errorString = i18n("START_CONFDIALOG does not take arguments");
return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString);
}
Q_EMIT conn.q->startConfigDialogRequested();
return assuan_process_done(ctx_, 0);
}
template<bool in>
struct Input_or_Output : std::conditional<in, Input, Output> {
};
// format: TAG (FD|FD=\d+|FILE=...)
template<bool in, typename T_memptr>
static gpg_error_t IO_handler(assuan_context_t ctx_, char *line_, T_memptr which)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
char *binOpt = strstr(line_, "--binary");
if (binOpt && !in) {
/* Note there is also --armor and --base64 allowed but we don't need
* to parse those because they are default.
* We remove it here so that it is not parsed as an Option.*/
memset(binOpt, ' ', 8);
}
try {
/*const*/ std::map<std::string, std::string> options = upcase_option("FD", upcase_option("FILE", parse_commandline(line_)));
if (options.size() < 1 || options.size() > 2) {
throw gpg_error(GPG_ERR_ASS_SYNTAX);
}
std::shared_ptr<typename Input_or_Output<in>::type> io;
if (options.count("FD")) {
if (options.count("FILE")) {
throw gpg_error(GPG_ERR_CONFLICT);
}
assuan_fd_t fd = ASSUAN_INVALID_FD;
const std::string fdstr = options["FD"];
if (fdstr.empty()) {
if (const gpg_error_t err = assuan_receivefd(conn.ctx.get(), &fd)) {
throw err;
}
} else {
#if defined(Q_OS_WIN)
fd = (assuan_fd_t)std::stoi(fdstr);
#else
fd = std::stoi(fdstr);
#endif
}
io = Input_or_Output<in>::type::createFromPipeDevice(fd, in ? i18n("Message #%1", (conn.*which).size() + 1) : QString());
options.erase("FD");
} else if (options.count("FILE")) {
if (options.count("FD")) {
throw gpg_error(GPG_ERR_CONFLICT);
}
const QString filePath = QFile::decodeName(options["FILE"].c_str());
if (filePath.isEmpty()) {
throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Empty file path"));
}
const QFileInfo fi(filePath);
if (!fi.isAbsolute()) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed"));
}
if (!fi.isFile()) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only files are allowed in INPUT/OUTPUT FILE"));
} else {
io = Input_or_Output<in>::type::createFromFile(fi.absoluteFilePath(), true);
}
options.erase("FILE");
} else {
throw gpg_error(GPG_ERR_ASS_PARAMETER);
}
if (options.size()) {
throw gpg_error(GPG_ERR_UNKNOWN_OPTION);
}
(conn.*which).push_back(io);
if (binOpt && !in) {
auto out = reinterpret_cast<Output *>(io.get());
out->setBinaryOpt(true);
qCDebug(KLEOPATRA_LOG) << "Configured output for binary data";
}
qCDebug(KLEOPATRA_LOG) << "AssuanServerConnection: added" << io->label();
return assuan_process_done(conn.ctx.get(), 0);
} catch (const GpgME::Exception &e) {
return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().c_str());
} catch (const std::exception &) {
return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_ASS_SYNTAX));
} catch (const gpg_error_t &e) {
return assuan_process_done(conn.ctx.get(), e);
} catch (...) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), "unknown exception caught");
}
}
static gpg_error_t input_handler(assuan_context_t ctx, char *line)
{
return IO_handler<true>(ctx, line, &Private::inputs);
}
static gpg_error_t output_handler(assuan_context_t ctx, char *line)
{
return IO_handler<false>(ctx, line, &Private::outputs);
}
static gpg_error_t message_handler(assuan_context_t ctx, char *line)
{
return IO_handler<true>(ctx, line, &Private::messages);
}
static gpg_error_t file_handler(assuan_context_t ctx_, char *line)
{
Q_ASSERT(assuan_get_pointer(ctx_));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx_));
try {
const QFileInfo fi(QFile::decodeName(hexdecode(line).c_str()));
if (!fi.isAbsolute()) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed"));
}
if (!fi.exists()) {
throw gpg_error(GPG_ERR_ENOENT);
}
if (!fi.isReadable() || (fi.isDir() && !fi.isExecutable())) {
throw gpg_error(GPG_ERR_EPERM);
}
conn.files.push_back(fi.absoluteFilePath());
return assuan_process_done(conn.ctx.get(), 0);
} catch (const Exception &e) {
return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().toUtf8().constData());
} catch (const gpg_error_t &e) {
return assuan_process_done(conn.ctx.get(), e);
} catch (...) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("unknown exception caught").toUtf8().constData());
}
}
static bool parse_informative(const char *&begin, GpgME::Protocol &protocol)
{
protocol = GpgME::UnknownProtocol;
bool informative = false;
const char *pos = begin;
while (true) {
while (*pos == ' ' || *pos == '\t') {
++pos;
}
if (qstrnicmp(pos, "--info", strlen("--info")) == 0) {
informative = true;
pos += strlen("--info");
if (*pos == '=') {
++pos;
break;
}
} else if (qstrnicmp(pos, "--protocol=", strlen("--protocol=")) == 0) {
pos += strlen("--protocol=");
if (qstrnicmp(pos, "OpenPGP", strlen("OpenPGP")) == 0) {
protocol = GpgME::OpenPGP;
pos += strlen("OpenPGP");
} else if (qstrnicmp(pos, "CMS", strlen("CMS")) == 0) {
protocol = GpgME::CMS;
pos += strlen("CMS");
} else {
;
}
} else if (qstrncmp(pos, "-- ", strlen("-- ")) == 0) {
pos += 3;
while (*pos == ' ' || *pos == '\t') {
++pos;
}
break;
} else {
break;
}
}
begin = pos;
return informative;
}
template<typename T_memptr, typename T_memptr2>
static gpg_error_t recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false)
{
Q_ASSERT(assuan_get_pointer(ctx));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx));
if (!line || !*line) {
return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG));
}
const char *begin = line;
const char *const end = begin + qstrlen(line);
GpgME::Protocol proto = GpgME::UnknownProtocol;
const bool informative = parse_informative(begin, proto);
if (!(conn.*mp).empty() && informative != (conn.*info))
return assuan_process_done_msg(conn.ctx.get(),
gpg_error(GPG_ERR_CONFLICT),
i18n("Cannot mix --info with non-info SENDER or RECIPIENT").toUtf8().constData());
KMime::Types::Mailbox mb;
if (!KMime::HeaderParsing::parseMailbox(begin, end, mb))
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG), i18n("Argument is not a valid RFC-2822 mailbox").toUtf8().constData());
if (begin != end)
return assuan_process_done_msg(conn.ctx.get(),
gpg_error(GPG_ERR_INV_ARG),
i18n("Garbage after valid RFC-2822 mailbox detected").toUtf8().constData());
(conn.*info) = informative;
(conn.*mp).push_back(mb);
const QString email = mb.addrSpec().asString();
(void)assuan_write_line(conn.ctx.get(), qPrintable(QString::asprintf("# ok, parsed as \"%s\"", qPrintable(email))));
if (sender && !informative) {
return AssuanCommandFactory::_handle(conn.ctx.get(), line, "PREP_SIGN");
} else {
return assuan_process_done(ctx, 0);
}
}
static gpg_error_t recipient_handler(assuan_context_t ctx, char *line)
{
return recipient_sender_handler(&Private::recipients, &Private::informativeRecipients, ctx, line);
}
static gpg_error_t sender_handler(assuan_context_t ctx, char *line)
{
return recipient_sender_handler(&Private::senders, &Private::informativeSenders, ctx, line, true);
}
QByteArray dumpOptions() const
{
QByteArray result;
for (auto it = options.begin(), end = options.end(); it != end; ++it) {
result += it->first.c_str() + it->second.toString().toUtf8() + '\n';
}
return result;
}
static QByteArray dumpStringList(const QStringList &sl)
{
return sl.join(QLatin1Char('\n')).toUtf8();
}
template<typename T_container>
static QByteArray dumpStringList(const T_container &c)
{
QStringList sl;
std::copy(c.begin(), c.end(), std::back_inserter(sl));
return dumpStringList(sl);
}
template<typename T_container>
static QByteArray dumpMailboxes(const T_container &c)
{
QStringList sl;
std::transform(c.begin(), c.end(), std::back_inserter(sl), [](typename T_container::const_reference val) {
return val.prettyAddress();
});
return dumpStringList(sl);
}
QByteArray dumpSenders() const
{
return dumpMailboxes(senders);
}
QByteArray dumpRecipients() const
{
return dumpMailboxes(recipients);
}
QByteArray dumpMementos() const
{
QByteArray result;
for (auto it = mementos.begin(), end = mementos.end(); it != end; ++it) {
char buf[2 + 2 * sizeof(void *) + 2];
sprintf(buf, "0x%p\n", (void *)it->second.get());
buf[sizeof(buf) - 1] = '\0';
result += it->first + QByteArray::fromRawData(buf, sizeof buf);
}
return result;
}
QByteArray dumpFiles() const
{
QStringList rv;
rv.reserve(files.size());
std::copy(files.cbegin(), files.cend(), std::back_inserter(rv));
return dumpStringList(rv);
}
void cleanup();
void reset()
{
options.clear();
senders.clear();
informativeSenders = false;
recipients.clear();
informativeRecipients = false;
sessionTitle.clear();
sessionId = 0;
mementos.clear();
files.clear();
std::for_each(inputs.begin(), inputs.end(), std::mem_fn(&Input::finalize));
inputs.clear();
std::for_each(outputs.begin(), outputs.end(), std::mem_fn(&Output::finalize));
outputs.clear();
std::for_each(messages.begin(), messages.end(), std::mem_fn(&Input::finalize));
messages.clear();
bias = GpgME::UnknownProtocol;
}
assuan_fd_t fd;
AssuanContext ctx;
bool closed : 1;
bool cryptoCommandsEnabled : 1;
bool commandWaitingForCryptoCommandsEnabled : 1;
bool currentCommandIsNohup : 1;
bool informativeSenders; // address taken, so no : 1
bool informativeRecipients; // address taken, so no : 1
GpgME::Protocol bias;
QString sessionTitle;
unsigned int sessionId;
std::vector<std::shared_ptr<QSocketNotifier>> notifiers;
std::vector<std::shared_ptr<AssuanCommandFactory>> factories; // sorted: _detail::ByName<std::less>
std::shared_ptr<AssuanCommand> currentCommand;
std::vector<std::shared_ptr<AssuanCommand>> nohupedCommands;
std::map<std::string, QVariant> options;
std::vector<KMime::Types::Mailbox> senders, recipients;
std::vector<std::shared_ptr<Input>> inputs, messages;
std::vector<std::shared_ptr<Output>> outputs;
std::vector<QString> files;
std::map<QByteArray, std::shared_ptr<AssuanCommand::Memento>> mementos;
};
void AssuanServerConnection::Private::cleanup()
{
Q_ASSERT(nohupedCommands.empty());
reset();
currentCommand.reset();
currentCommandIsNohup = false;
commandWaitingForCryptoCommandsEnabled = false;
notifiers.clear();
ctx.reset();
fd = ASSUAN_INVALID_FD;
}
AssuanServerConnection::Private::Private(assuan_fd_t fd_, const std::vector<std::shared_ptr<AssuanCommandFactory>> &factories_, AssuanServerConnection *qq)
: QObject()
, q(qq)
, fd(fd_)
, closed(false)
, cryptoCommandsEnabled(false)
, commandWaitingForCryptoCommandsEnabled(false)
, currentCommandIsNohup(false)
, informativeSenders(false)
, informativeRecipients(false)
, bias(GpgME::UnknownProtocol)
, sessionId(0)
, factories(factories_)
{
#ifdef __GLIBCXX__
Q_ASSERT(__gnu_cxx::is_sorted(factories_.begin(), factories_.end(), _detail::ByName<std::less>()));
#endif
if (fd == ASSUAN_INVALID_FD) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), "pre-assuan_init_socket_server_ext");
}
{
assuan_context_t naked_ctx = nullptr;
if (const gpg_error_t err = assuan_new(&naked_ctx)) {
throw Exception(err, "assuan_new");
}
ctx.reset(naked_ctx);
}
if (const gpg_error_t err = assuan_init_socket_server(ctx.get(), fd, INIT_SOCKET_FLAGS))
throw Exception(err, "assuan_init_socket_server_ext");
// for callbacks, associate the context with this connection:
assuan_set_pointer(ctx.get(), this);
FILE *const logFile = Log::instance()->logFile();
assuan_set_log_stream(ctx.get(), logFile ? logFile : stderr);
// register FDs with the event loop:
assuan_fd_t fds[MAX_ACTIVE_FDS];
const int numFDs = assuan_get_active_fds(ctx.get(), FOR_READING, fds, MAX_ACTIVE_FDS);
Q_ASSERT(numFDs != -1); // == 1
if (!numFDs || fds[0] != fd) {
const std::shared_ptr<QSocketNotifier> sn(new QSocketNotifier((intptr_t)fd, QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater));
connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity);
notifiers.push_back(sn);
}
notifiers.reserve(notifiers.size() + numFDs);
for (int i = 0; i < numFDs; ++i) {
const std::shared_ptr<QSocketNotifier> sn(new QSocketNotifier((intptr_t)fds[i], QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater));
connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity);
notifiers.push_back(sn);
}
// register our INPUT/OUTPUT/MESSGAE/FILE handlers:
if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler, ""))
throw Exception(err, "register \"INPUT\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler, ""))
throw Exception(err, "register \"MESSAGE\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler, ""))
throw Exception(err, "register \"OUTPUT\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler, ""))
throw Exception(err, "register \"FILE\" handler");
// register user-defined commands:
for (std::shared_ptr<AssuanCommandFactory> fac : std::as_const(factories))
if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler(), ""))
throw Exception(err, std::string("register \"") + fac->name() + "\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler, ""))
throw Exception(err, "register \"GETINFO\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler, ""))
throw Exception(err, "register \"START_KEYMANAGER\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler, ""))
throw Exception(err, "register \"START_CONFDIALOG\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler, ""))
throw Exception(err, "register \"RECIPIENT\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler, ""))
throw Exception(err, "register \"SENDER\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler, ""))
throw Exception(err, "register \"SESSION\" handler");
if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler, ""))
throw Exception(err, "register \"CAPABILITIES\" handler");
assuan_set_hello_line(ctx.get(), "GPG UI server (Kleopatra/" KLEOPATRA_VERSION_STRING ") ready to serve");
// assuan_set_hello_line( ctx.get(), GPG UI server (qApp->applicationName() + " v" + kapp->applicationVersion() + "ready to serve" )
// some notifiers we're interested in:
if (const gpg_error_t err = assuan_register_reset_notify(ctx.get(), reset_handler)) {
throw Exception(err, "register reset notify");
}
if (const gpg_error_t err = assuan_register_option_handler(ctx.get(), option_handler)) {
throw Exception(err, "register option handler");
}
// and last, we need to call assuan_accept, which doesn't block
// (d/t INIT_SOCKET_FLAGS), but performs vital connection
// establishing handling:
if (const gpg_error_t err = assuan_accept(ctx.get())) {
throw Exception(err, "assuan_accept");
}
}
AssuanServerConnection::Private::~Private()
{
cleanup();
}
AssuanServerConnection::AssuanServerConnection(assuan_fd_t fd, const std::vector<std::shared_ptr<AssuanCommandFactory>> &factories, QObject *p)
: QObject(p)
, d(new Private(fd, factories, this))
{
}
AssuanServerConnection::~AssuanServerConnection()
{
}
void AssuanServerConnection::enableCryptoCommands(bool on)
{
if (on == d->cryptoCommandsEnabled) {
return;
}
d->cryptoCommandsEnabled = on;
if (d->commandWaitingForCryptoCommandsEnabled) {
QTimer::singleShot(0, d.get(), &Private::startCommandBottomHalf);
}
}
//
//
// AssuanCommand:
//
//
namespace Kleo
{
class InquiryHandler : public QObject
{
Q_OBJECT
public:
explicit InquiryHandler(const char *keyword_, QObject *p = nullptr)
: QObject(p)
, keyword(keyword_)
{
}
static gpg_error_t handler(void *cb_data, gpg_error_t rc, unsigned char *buffer, size_t buflen)
{
Q_ASSERT(cb_data);
auto this_ = static_cast<InquiryHandler *>(cb_data);
Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast<const char *>(buffer), buflen), this_->keyword);
std::free(buffer);
delete this_;
return 0;
}
private:
const char *keyword;
Q_SIGNALS:
void signal(int rc, const QByteArray &data, const QByteArray &keyword);
};
} // namespace Kleo
class AssuanCommand::Private
{
public:
Private()
: informativeRecipients(false)
, informativeSenders(false)
, bias(GpgME::UnknownProtocol)
, done(false)
, nohup(false)
{
}
std::map<std::string, QVariant> options;
std::vector<std::shared_ptr<Input>> inputs, messages;
std::vector<std::shared_ptr<Output>> outputs;
std::vector<QString> files;
std::vector<KMime::Types::Mailbox> recipients, senders;
bool informativeRecipients, informativeSenders;
GpgME::Protocol bias;
QString sessionTitle;
unsigned int sessionId;
QByteArray utf8ErrorKeepAlive;
AssuanContext ctx;
bool done;
bool nohup;
};
AssuanCommand::AssuanCommand()
: d(new Private)
{
}
AssuanCommand::~AssuanCommand()
{
}
int AssuanCommand::start()
{
try {
if (const int err = doStart())
if (!d->done) {
done(err);
}
return 0;
} catch (const Exception &e) {
if (!d->done) {
done(e.error_code(), e.message());
}
return 0;
} catch (const GpgME::Exception &e) {
if (!d->done) {
done(e.error(), QString::fromLocal8Bit(e.message().c_str()));
}
return 0;
} catch (const std::exception &e) {
if (!d->done) {
done(makeError(GPG_ERR_INTERNAL), i18n("Caught unexpected exception: %1", QString::fromLocal8Bit(e.what())));
}
return 0;
} catch (...) {
if (!d->done) {
done(makeError(GPG_ERR_INTERNAL), i18n("Caught unknown exception - please report this error to the developers."));
}
return 0;
}
}
void AssuanCommand::canceled()
{
d->done = true;
doCanceled();
}
// static
int AssuanCommand::makeError(int code)
{
return makeGnuPGError(code);
}
bool AssuanCommand::hasOption(const char *opt) const
{
return d->options.count(opt);
}
QVariant AssuanCommand::option(const char *opt) const
{
const auto it = d->options.find(opt);
if (it == d->options.end()) {
return QVariant();
} else {
return it->second;
}
}
const std::map<std::string, QVariant> &AssuanCommand::options() const
{
return d->options;
}
namespace
{
template<typename U, typename V>
std::vector<U> keys(const std::map<U, V> &map)
{
std::vector<U> result;
result.resize(map.size());
for (typename std::map<U, V>::const_iterator it = map.begin(), end = map.end(); it != end; ++it) {
result.push_back(it->first);
}
return result;
}
}
const std::map<QByteArray, std::shared_ptr<AssuanCommand::Memento>> &AssuanCommand::mementos() const
{
// oh, hack :(
Q_ASSERT(assuan_get_pointer(d->ctx.get()));
const AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
return conn.mementos;
}
bool AssuanCommand::hasMemento(const QByteArray &tag) const
{
if (const unsigned int id = sessionId()) {
return SessionDataHandler::instance()->sessionData(id)->mementos.count(tag) || mementos().count(tag);
} else {
return mementos().count(tag);
}
}
std::shared_ptr<AssuanCommand::Memento> AssuanCommand::memento(const QByteArray &tag) const
{
if (const unsigned int id = sessionId()) {
const std::shared_ptr<SessionDataHandler> sdh = SessionDataHandler::instance();
const std::shared_ptr<SessionData> sd = sdh->sessionData(id);
const auto it = sd->mementos.find(tag);
if (it != sd->mementos.end()) {
return it->second;
}
}
const auto it = mementos().find(tag);
if (it == mementos().end()) {
return std::shared_ptr<Memento>();
} else {
return it->second;
}
}
QByteArray AssuanCommand::registerMemento(const std::shared_ptr<Memento> &mem)
{
const QByteArray tag = QByteArray::number(reinterpret_cast<qulonglong>(mem.get()), 36);
return registerMemento(tag, mem);
}
QByteArray AssuanCommand::registerMemento(const QByteArray &tag, const std::shared_ptr<Memento> &mem)
{
// oh, hack :(
Q_ASSERT(assuan_get_pointer(d->ctx.get()));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
if (const unsigned int id = sessionId()) {
SessionDataHandler::instance()->sessionData(id)->mementos[tag] = mem;
} else {
conn.mementos[tag] = mem;
}
return tag;
}
void AssuanCommand::removeMemento(const QByteArray &tag)
{
// oh, hack :(
Q_ASSERT(assuan_get_pointer(d->ctx.get()));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
conn.mementos.erase(tag);
if (const unsigned int id = sessionId()) {
SessionDataHandler::instance()->sessionData(id)->mementos.erase(tag);
}
}
const std::vector<std::shared_ptr<Input>> &AssuanCommand::inputs() const
{
return d->inputs;
}
const std::vector<std::shared_ptr<Input>> &AssuanCommand::messages() const
{
return d->messages;
}
const std::vector<std::shared_ptr<Output>> &AssuanCommand::outputs() const
{
return d->outputs;
}
QStringList AssuanCommand::fileNames() const
{
QStringList rv;
rv.reserve(d->files.size());
std::copy(d->files.cbegin(), d->files.cend(), std::back_inserter(rv));
return rv;
}
unsigned int AssuanCommand::numFiles() const
{
return d->files.size();
}
void AssuanCommand::sendStatus(const char *keyword, const QString &text)
{
sendStatusEncoded(keyword, text.toUtf8().constData());
}
void AssuanCommand::sendStatusEncoded(const char *keyword, const std::string &text)
{
if (d->nohup) {
return;
}
if (const int err = assuan_write_status(d->ctx.get(), keyword, text.c_str())) {
throw Exception(err, i18n("Cannot send \"%1\" status", QString::fromLatin1(keyword)));
}
}
void AssuanCommand::sendData(const QByteArray &data, bool moreToCome)
{
if (d->nohup) {
return;
}
if (const gpg_error_t err = assuan_send_data(d->ctx.get(), data.constData(), data.size())) {
throw Exception(err, i18n("Cannot send data"));
}
if (!moreToCome)
if (const gpg_error_t err = assuan_send_data(d->ctx.get(), nullptr, 0)) { // flush
throw Exception(err, i18n("Cannot flush data"));
}
}
int AssuanCommand::inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize)
{
Q_ASSERT(keyword);
Q_ASSERT(receiver);
Q_ASSERT(slot);
if (d->nohup) {
return makeError(GPG_ERR_INV_OP);
}
std::unique_ptr<InquiryHandler> ih(new InquiryHandler(keyword, receiver));
receiver->connect(ih.get(), SIGNAL(signal(int, QByteArray, QByteArray)), slot);
if (const gpg_error_t err = assuan_inquire_ext(d->ctx.get(), keyword, maxSize, InquiryHandler::handler, ih.get())) {
return err;
}
ih.release();
return 0;
}
void AssuanCommand::done(const GpgME::Error &err, const QString &details)
{
if (d->ctx && !d->done && !details.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "Error: " << details;
d->utf8ErrorKeepAlive = details.toUtf8();
if (!d->nohup) {
assuan_set_error(d->ctx.get(), err.encodedError(), d->utf8ErrorKeepAlive.constData());
}
}
done(err);
}
void AssuanCommand::done(const GpgME::Error &err)
{
if (!d->ctx) {
qCDebug(KLEOPATRA_LOG) << Formatting::errorAsString(err) << ": called with NULL ctx.";
return;
}
if (d->done) {
qCDebug(KLEOPATRA_LOG) << Formatting::errorAsString(err) << ": called twice!";
return;
}
d->done = true;
std::for_each(d->messages.begin(), d->messages.end(), std::mem_fn(&Input::finalize));
std::for_each(d->inputs.begin(), d->inputs.end(), std::mem_fn(&Input::finalize));
std::for_each(d->outputs.begin(), d->outputs.end(), std::mem_fn(&Output::finalize));
d->messages.clear();
d->inputs.clear();
d->outputs.clear();
d->files.clear();
// oh, hack :(
Q_ASSERT(assuan_get_pointer(d->ctx.get()));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(d->ctx.get()));
if (d->nohup) {
conn.nohupDone(this);
return;
}
const gpg_error_t rc = assuan_process_done(d->ctx.get(), err.encodedError());
if (gpg_err_code(rc) != GPG_ERR_NO_ERROR)
qFatal("AssuanCommand::done: assuan_process_done returned error %d (%s)", static_cast<int>(rc), gpg_strerror(rc));
d->utf8ErrorKeepAlive.clear();
conn.commandDone(this);
}
void AssuanCommand::setNohup(bool nohup)
{
d->nohup = nohup;
}
bool AssuanCommand::isNohup() const
{
return d->nohup;
}
bool AssuanCommand::isDone() const
{
return d->done;
}
QString AssuanCommand::sessionTitle() const
{
return d->sessionTitle;
}
unsigned int AssuanCommand::sessionId() const
{
return d->sessionId;
}
bool AssuanCommand::informativeSenders() const
{
return d->informativeSenders;
}
bool AssuanCommand::informativeRecipients() const
{
return d->informativeRecipients;
}
const std::vector<KMime::Types::Mailbox> &AssuanCommand::recipients() const
{
return d->recipients;
}
const std::vector<KMime::Types::Mailbox> &AssuanCommand::senders() const
{
return d->senders;
}
gpg_error_t AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName)
{
Q_ASSERT(assuan_get_pointer(ctx));
AssuanServerConnection::Private &conn = *static_cast<AssuanServerConnection::Private *>(assuan_get_pointer(ctx));
try {
const auto it = std::lower_bound(conn.factories.begin(), conn.factories.end(), commandName, _detail::ByName<std::less>());
kleo_assert(it != conn.factories.end());
kleo_assert(*it);
kleo_assert(qstricmp((*it)->name(), commandName) == 0);
const std::shared_ptr<AssuanCommand> cmd = (*it)->create();
kleo_assert(cmd);
cmd->d->ctx = conn.ctx;
cmd->d->options = conn.options;
cmd->d->inputs.swap(conn.inputs);
kleo_assert(conn.inputs.empty());
cmd->d->messages.swap(conn.messages);
kleo_assert(conn.messages.empty());
cmd->d->outputs.swap(conn.outputs);
kleo_assert(conn.outputs.empty());
cmd->d->files.swap(conn.files);
kleo_assert(conn.files.empty());
cmd->d->senders.swap(conn.senders);
kleo_assert(conn.senders.empty());
cmd->d->recipients.swap(conn.recipients);
kleo_assert(conn.recipients.empty());
cmd->d->informativeRecipients = conn.informativeRecipients;
cmd->d->informativeSenders = conn.informativeSenders;
cmd->d->bias = conn.bias;
cmd->d->sessionTitle = conn.sessionTitle;
cmd->d->sessionId = conn.sessionId;
const std::map<std::string, std::string> cmdline_options = parse_commandline(line);
for (auto it = cmdline_options.begin(), end = cmdline_options.end(); it != end; ++it) {
cmd->d->options[it->first] = QString::fromUtf8(it->second.c_str());
}
bool nohup = false;
if (cmd->d->options.count("nohup")) {
if (!cmd->d->options["nohup"].toString().isEmpty()) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_ASS_PARAMETER), "--nohup takes no argument");
}
nohup = true;
cmd->d->options.erase("nohup");
}
conn.currentCommand = cmd;
conn.currentCommandIsNohup = nohup;
QTimer::singleShot(0, &conn, &AssuanServerConnection::Private::startCommandBottomHalf);
return 0;
} catch (const Exception &e) {
return assuan_process_done_msg(conn.ctx.get(), e.error_code(), e.message());
} catch (const std::exception &e) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what());
} catch (...) {
return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception"));
}
}
int AssuanServerConnection::Private::startCommandBottomHalf()
{
commandWaitingForCryptoCommandsEnabled = currentCommand && !cryptoCommandsEnabled;
if (!cryptoCommandsEnabled) {
return 0;
}
const std::shared_ptr<AssuanCommand> cmd = currentCommand;
if (!cmd) {
return 0;
}
currentCommand.reset();
const bool nohup = currentCommandIsNohup;
currentCommandIsNohup = false;
try {
if (const int err = cmd->start()) {
if (cmd->isDone()) {
return err;
} else {
return assuan_process_done(ctx.get(), err);
}
}
if (cmd->isDone()) {
return 0;
}
if (nohup) {
cmd->setNohup(true);
nohupedCommands.push_back(cmd);
return assuan_process_done_msg(ctx.get(), 0, "Command put in the background to continue executing after connection end.");
} else {
currentCommand = cmd;
return 0;
}
} catch (const Exception &e) {
return assuan_process_done_msg(ctx.get(), e.error_code(), e.message());
} catch (const std::exception &e) {
return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what());
} catch (...) {
return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception"));
}
}
//
//
// AssuanCommand convenience methods
//
//
/*!
Checks the \c --mode parameter.
\returns The parameter as an AssuanCommand::Mode enum value.
If no \c --mode was given, or it's value wasn't recognized, throws
an Kleo::Exception.
*/
AssuanCommand::Mode AssuanCommand::checkMode() const
{
if (!hasOption("mode")) {
throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --mode option missing"));
}
const QString modeString = option("mode").toString().toLower();
if (modeString == QLatin1StringView("filemanager")) {
return FileManager;
}
if (modeString == QLatin1StringView("email")) {
return EMail;
}
throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid mode: \"%1\"", modeString));
}
/*!
Checks the \c --protocol parameter.
\returns The parameter as a GpgME::Protocol enum value.
If \c --protocol was given, but has an invalid value, throws an
Kleo::Exception.
If no \c --protocol was given, checks the connection bias, if
available, otherwise, in FileManager mode, returns
GpgME::UnknownProtocol, but if \a mode == \c EMail, throws an
Kleo::Exception instead.
*/
GpgME::Protocol AssuanCommand::checkProtocol(Mode mode, int options) const
{
if (!hasOption("protocol"))
if (d->bias != GpgME::UnknownProtocol) {
return d->bias;
} else if (mode == AssuanCommand::EMail && (options & AllowProtocolMissing) == 0) {
throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --protocol option missing"));
} else {
return GpgME::UnknownProtocol;
}
else if (mode == AssuanCommand::FileManager) {
throw Exception(makeError(GPG_ERR_INV_FLAG), i18n("--protocol is not allowed here"));
}
const QString protocolString = option("protocol").toString().toLower();
if (protocolString == QLatin1StringView("openpgp")) {
return GpgME::OpenPGP;
}
if (protocolString == QLatin1StringView("cms")) {
return GpgME::CMS;
}
throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid protocol \"%1\"", protocolString));
}
void AssuanCommand::doApplyWindowID(QWidget *widget) const
{
if (!widget || !hasOption("window-id")) {
return;
}
apply_window_id(widget, option("window-id").toString());
}
WId AssuanCommand::parentWId() const
{
return wid_from_string(option("window-id").toString());
}
#include "assuanserverconnection.moc"
#include "moc_assuanserverconnection.cpp"
diff --git a/src/utils/kuniqueservice_win.cpp b/src/utils/kuniqueservice_win.cpp
index ee1fd88d5..386ee9bd4 100644
--- a/src/utils/kuniqueservice_win.cpp
+++ b/src/utils/kuniqueservice_win.cpp
@@ -1,200 +1,200 @@
/*
kuniqueservice_win.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "kuniqueservice.h"
#include <QCoreApplication>
#include <QDataStream>
#include <QDir>
#include <KLocalizedString>
#include <KMessageBox>
#include "kleopatra_debug.h"
#include <windows.h>
#define MY_DATA_TYPE 12
class KUniqueService::KUniqueServicePrivate
{
Q_DECLARE_PUBLIC(KUniqueService)
Q_DISABLE_COPY(KUniqueServicePrivate)
private:
KUniqueService *q_ptr;
HWND mResponder;
HANDLE mResponderProc;
const QString getWindowName() const
{
return QCoreApplication::applicationName() + QStringLiteral("Responder");
}
HWND getForeignResponder() const
{
const QString qWndName = getWindowName();
wchar_t *wndName = (wchar_t *)qWndName.utf16();
HWND ret = FindWindow(wndName, wndName);
qCDebug(KLEOPATRA_LOG) << "Responder handle:" << ret;
return ret;
}
void createResponder()
{
WNDCLASS windowClass;
const QString qWndName = getWindowName();
wchar_t *wndName = (wchar_t *)qWndName.utf16();
windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
windowClass.lpfnWndProc = windowProc;
windowClass.hInstance = (HINSTANCE)GetModuleHandle(NULL);
windowClass.lpszClassName = wndName;
windowClass.hIcon = nullptr;
windowClass.hCursor = nullptr;
windowClass.hbrBackground = nullptr;
windowClass.lpszMenuName = nullptr;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
RegisterClass(&windowClass);
mResponder = CreateWindow(wndName, wndName, 0, 0, 0, 10, 10, nullptr, nullptr, (HINSTANCE)GetModuleHandle(NULL), nullptr);
qCDebug(KLEOPATRA_LOG) << "Created responder: " << qWndName << " with handle: " << mResponder;
}
void handleRequest(const COPYDATASTRUCT *cds)
{
Q_Q(KUniqueService);
if (cds->dwData != MY_DATA_TYPE) {
qCDebug(KLEOPATRA_LOG) << "Responder called with invalid data type:" << cds->dwData;
return;
}
if (mResponderProc) {
qCDebug(KLEOPATRA_LOG) << "Already serving. Terminating process: " << mResponderProc;
setExitValue(42);
}
const QByteArray serialized(static_cast<const char *>(cds->lpData), cds->cbData);
QDataStream ds(serialized);
- quint32 curProc;
+ intptr_t curProc;
ds >> curProc;
- mResponderProc = (HANDLE)curProc;
+ mResponderProc = reinterpret_cast<HANDLE>(curProc);
QString workDir;
ds >> workDir;
QStringList args;
ds >> args;
qCDebug(KLEOPATRA_LOG) << "Process handle: " << mResponderProc << " requests activate with args " << args;
q->emitActivateRequested(args, workDir);
return;
}
KUniqueServicePrivate(KUniqueService *q)
: q_ptr(q)
, mResponder(nullptr)
, mResponderProc(nullptr)
{
HWND responder = getForeignResponder();
if (!responder) {
// We are the responder
createResponder();
return;
}
// We are the client
QByteArray serialized;
QDataStream ds(&serialized, QIODevice::WriteOnly);
DWORD responderId = 0;
GetWindowThreadProcessId(responder, &responderId);
if (!responderId) {
qCDebug(KLEOPATRA_LOG) << "No id of responder window";
return;
}
// To allow the other process to terminate the process
// needs a handle to us with the required access.
int err = 0;
HANDLE responderHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, responderId);
if (!responderHandle) {
qCDebug(KLEOPATRA_LOG) << "Open process returned NULL. Err: " << GetLastError();
err = 1;
} else if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), responderHandle, &mResponderProc, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
qCDebug(KLEOPATRA_LOG) << "Duplicate Handle failed. Err: " << GetLastError();
err = 2;
}
if (err) {
KMessageBox::error(nullptr,
xi18nc("@info",
"<para><application>Kleopatra</application> seems to be running for you already, but with different privileges.</para>"
"<para>This usually happens if <application>Kleopatra</application> is accidentally run as Administrator.</para>"
"<para>Please right click the tray icon of <application>Kleopatra</application> and select 'quit' to try again.</para>"),
xi18nc("@title", "<application>Kleopatra</application> failed to start"));
exit(err);
}
CloseHandle(responderHandle);
- ds << (qint32)mResponderProc << QDir::currentPath() << QCoreApplication::arguments();
+ ds << reinterpret_cast<intptr_t>(mResponderProc) << QDir::currentPath() << QCoreApplication::arguments();
COPYDATASTRUCT cds;
cds.dwData = MY_DATA_TYPE;
cds.cbData = serialized.size();
cds.lpData = serialized.data();
qCDebug(KLEOPATRA_LOG) << "Sending message to existing Window.";
SendMessage(responder, WM_COPYDATA, 0, (LPARAM)&cds);
// Usually we should be terminated while sending the message.
qCDebug(KLEOPATRA_LOG) << "Send message returned.";
}
static KUniqueServicePrivate *instance(KUniqueService *q)
{
static KUniqueServicePrivate *self;
if (self) {
return self;
}
self = new KUniqueServicePrivate(q);
return self;
}
static LRESULT CALLBACK windowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_COPYDATA) {
const COPYDATASTRUCT *cds = (COPYDATASTRUCT *)lParam;
// windowProc must be static so the singleton pattern although
// it doesn't make much sense in here.
instance(nullptr)->handleRequest(cds);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
~KUniqueServicePrivate()
{
if (mResponder) {
DestroyWindow(mResponder);
}
}
void setExitValue(int code)
{
TerminateProcess(mResponderProc, (unsigned int)code);
mResponderProc = nullptr;
}
};
KUniqueService::KUniqueService()
: d_ptr(KUniqueServicePrivate::instance(this))
{
}
KUniqueService::~KUniqueService()
{
delete d_ptr;
}
void KUniqueService::setExitValue(int code)
{
Q_D(KUniqueService);
d->setExitValue(code);
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Dec 11, 1:03 AM (2 h, 37 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
7f/83/7f8c9a27da4e003f990881291d7e

Event Timeline