Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34156460
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
85 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment