diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -21,6 +21,7 @@ ../src/kauthaction.cpp ../src/kauthactionreply.cpp ../src/kauthexecutejob.cpp + ../src/kauthunixfiledescriptor.cpp ../src/AuthBackend.cpp # Use our "special" backends manager BackendsManager.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,7 @@ kauthaction.cpp kauthactionreply.cpp kauthexecutejob.cpp + kauthunixfiledescriptor.cpp AuthBackend.cpp BackendsManager.cpp HelperProxy.cpp @@ -63,6 +64,7 @@ KAuthActionReply KAuthExecuteJob KAuthObjectDecorator + KAuthUnixFileDescriptor REQUIRED_HEADERS KAuth_HEADERS ) @@ -95,6 +97,7 @@ kauthactionreply.h kauthexecutejob.h kauthobjectdecorator.h + kauthunixfiledescriptor.h ) endif() diff --git a/src/HelperProxy.h b/src/HelperProxy.h --- a/src/HelperProxy.h +++ b/src/HelperProxy.h @@ -50,6 +50,7 @@ virtual void sendDebugMessage(int level, const char *msg) = 0; virtual void sendProgressStep(int step) = 0; virtual void sendProgressStep(const QVariantMap &step) = 0; + virtual bool isUnixFileDescriptorPassingSupported() const = 0; Q_SIGNALS: void actionStarted(const QString &action); diff --git a/src/backends/dbus/DBusHelperProxy.h b/src/backends/dbus/DBusHelperProxy.h --- a/src/backends/dbus/DBusHelperProxy.h +++ b/src/backends/dbus/DBusHelperProxy.h @@ -45,11 +45,11 @@ QDBusConnection m_busConnection; enum SignalType { - ActionStarted, // The blob argument is empty - ActionPerformed, // The blob argument contains the ActionReply - DebugMessage, // The blob argument contains the debug level and the message (in this order) - ProgressStepIndicator, // The blob argument contains the step indicator - ProgressStepData // The blob argument contains the QVariantMap + ActionStarted, // The data argument is empty + ActionPerformed, // The data argument contains the ActionReply + DebugMessage, // The data argument contains the debug level and the message (in this order) + ProgressStepIndicator, // The data argument contains the step indicator + ProgressStepData // The data argument contains the QVariantMap }; public: @@ -68,16 +68,17 @@ void sendDebugMessage(int level, const char *msg) override; void sendProgressStep(int step) override; void sendProgressStep(const QVariantMap &data) override; + bool isUnixFileDescriptorPassingSupported() const override; public Q_SLOTS: void stopAction(const QString &action); - QByteArray performAction(const QString &action, const QByteArray &callerID, QByteArray arguments); + QVariantList performAction(const QString &action, const QByteArray &callerID, QVariantList arguments); Q_SIGNALS: - void remoteSignal(int type, const QString &action, const QByteArray &blob); // This signal is sent from the helper to the app + void remoteSignal(int type, const QString &action, const QVariantList &data); // This signal is sent from the helper to the app private Q_SLOTS: - void remoteSignalReceived(int type, const QString &action, QByteArray blob); + void remoteSignalReceived(int type, const QString &action, QVariantList data); private: bool isCallerAuthorized(const QString &action, const QByteArray &callerID); diff --git a/src/backends/dbus/DBusHelperProxy.cpp b/src/backends/dbus/DBusHelperProxy.cpp --- a/src/backends/dbus/DBusHelperProxy.cpp +++ b/src/backends/dbus/DBusHelperProxy.cpp @@ -25,18 +25,25 @@ #include #include #include +#include +#include #include #include "BackendsManager.h" #include "kf5authadaptor.h" #include "kauthdebug.h" +#include "kauthunixfiledescriptor.h" extern Q_CORE_EXPORT const QMetaTypeInterface *qMetaTypeGuiHelper; namespace KAuth { static void debugMessageReceived(int t, const QString &message); +static QVariantList packedArguments(const QVariantMap &arguments); +static QVariantMap unpackedArguments(const QVariantList &arguments); +static QVariantList packedReply(const ActionReply &reply); +static ActionReply unpackReply(const QVariantList &packedData); DBusHelperProxy::DBusHelperProxy() : responder(nullptr) @@ -70,12 +77,6 @@ void DBusHelperProxy::executeAction(const QString &action, const QString &helperID, const QVariantMap &arguments, int timeout) { - QByteArray blob; - { - QDataStream stream(&blob, QIODevice::WriteOnly); - stream << arguments; - } - //on unit tests we won't have a service, but the service will already be running const auto reply = m_busConnection.interface()->startService(helperID); if (!reply.isValid() && !m_busConnection.interface()->isServiceRegistered(helperID)) { @@ -85,7 +86,7 @@ return; } - const bool connected = m_busConnection.connect(helperID, QLatin1String("/"), QLatin1String("org.kde.kf5auth"), QLatin1String("remoteSignal"), this, SLOT(remoteSignalReceived(int,QString,QByteArray))); + const bool connected = m_busConnection.connect(helperID, QLatin1String("/"), QLatin1String("org.kde.kf5auth"), QLatin1String("remoteSignal"), this, SLOT(remoteSignalReceived(int,QString,QVariantList))); //if already connected reply will be false but we won't have an error or a reason to fail if (!connected && m_busConnection.lastError().isValid()) { @@ -98,11 +99,19 @@ return; } + QVariantList actionArgs = packedArguments(arguments); + if (!isUnixFileDescriptorPassingSupported() && actionArgs.count() > 1) { + ActionReply errorReply = ActionReply::DBusErrorReply(); + errorReply.setErrorDescription(tr("DBus Backend error: passing of Unix file descriptors is not supported")); + emit actionPerformed(action, errorReply); + return; + } + QDBusMessage message; message = QDBusMessage::createMethodCall(helperID, QLatin1String("/"), QLatin1String("org.kde.kf5auth"), QLatin1String("performAction")); QList args; - args << action << BackendsManager::authBackend()->callerID() << blob; + args << action << BackendsManager::authBackend()->callerID() << QVariant::fromValue(actionArgs); message.setArguments(args); m_actionsInProgress.push_back(action); @@ -151,35 +160,28 @@ responder = o; } -void DBusHelperProxy::remoteSignalReceived(int t, const QString &action, QByteArray blob) +void DBusHelperProxy::remoteSignalReceived(int t, const QString &action, QVariantList data) { SignalType type = static_cast(t); - QDataStream stream(&blob, QIODevice::ReadOnly); if (type == ActionStarted) { emit actionStarted(action); } else if (type == ActionPerformed) { - ActionReply reply = ActionReply::deserialize(blob); + ActionReply reply = unpackReply(data); m_actionsInProgress.removeOne(action); emit actionPerformed(action, reply); } else if (type == DebugMessage) { - int level; - QString message; - - stream >> level >> message; + int level = data.value(0).toInt(); + QString message = data.value(1).toString(); debugMessageReceived(level, message); } else if (type == ProgressStepIndicator) { - int step; - stream >> step; + int step = data.value(0).toInt(); emit progressStep(action, step); } else if (type == ProgressStepData) { - QVariantMap data; - stream >> data; - - emit progressStep(action, data); + emit progressStep(action, unpackedArguments(data)); } } @@ -221,29 +223,20 @@ return BackendsManager::authBackend()->isCallerAuthorized(action, callerID); } -QByteArray DBusHelperProxy::performAction(const QString &action, const QByteArray &callerID, QByteArray arguments) +QVariantList DBusHelperProxy::performAction(const QString &action, const QByteArray &callerID, QVariantList arguments) { if (!responder) { - return ActionReply::NoResponderReply().serialized(); + return packedReply(ActionReply::NoResponderReply()); } if (!m_currentAction.isEmpty()) { - return ActionReply::HelperBusyReply().serialized(); + return packedReply(ActionReply::HelperBusyReply()); } - // Make sure we don't try restoring gui variants, in particular QImage/QPixmap/QIcon are super dangerous - // since they end up calling the image loaders and thus are a vector for crashing → executing code - auto origMetaTypeGuiHelper = qMetaTypeGuiHelper; - qMetaTypeGuiHelper = nullptr; - - QVariantMap args; - QDataStream s(&arguments, QIODevice::ReadOnly); - s >> args; - - qMetaTypeGuiHelper = origMetaTypeGuiHelper; + QVariantMap args = unpackedArguments(arguments); m_currentAction = action; - emit remoteSignal(ActionStarted, action, QByteArray()); + emit remoteSignal(ActionStarted, action, QVariantList()); QEventLoop e; e.processEvents(QEventLoop::AllEvents); @@ -273,42 +266,33 @@ timer->start(); - emit remoteSignal(ActionPerformed, action, retVal.serialized()); + const auto result = packedReply(retVal); + emit remoteSignal(ActionPerformed, action, result); e.processEvents(QEventLoop::AllEvents); m_currentAction.clear(); m_stopRequest = false; - return retVal.serialized(); + return result; } void DBusHelperProxy::sendDebugMessage(int level, const char *msg) { - QByteArray blob; - QDataStream stream(&blob, QIODevice::WriteOnly); - - stream << level << QString::fromLocal8Bit(msg); - - emit remoteSignal(DebugMessage, m_currentAction, blob); + emit remoteSignal(DebugMessage, m_currentAction, QVariantList() << level << QString::fromLocal8Bit(msg)); } void DBusHelperProxy::sendProgressStep(int step) { - QByteArray blob; - QDataStream stream(&blob, QIODevice::WriteOnly); - - stream << step; - - emit remoteSignal(ProgressStepIndicator, m_currentAction, blob); + emit remoteSignal(ProgressStepIndicator, m_currentAction, QVariantList() << step); } void DBusHelperProxy::sendProgressStep(const QVariantMap &data) { - QByteArray blob; - QDataStream stream(&blob, QIODevice::WriteOnly); - - stream << data; + emit remoteSignal(ProgressStepData, m_currentAction, packedArguments(data)); +} - emit remoteSignal(ProgressStepData, m_currentAction, blob); +bool DBusHelperProxy::isUnixFileDescriptorPassingSupported() const +{ + return m_busConnection.connectionCapabilities() & QDBusConnection::UnixFileDescriptorPassing; } void debugMessageReceived(int t, const QString &message) @@ -333,4 +317,99 @@ } } +QVariantList packedArguments(const QVariantMap &arguments) +{ + QVariantList result; + + QVariantMap blobArgs; + QVariantMap fdArgs; + for (auto it = arguments.begin(); it != arguments.end(); ++it) { + if (it->canConvert()) { + int fd = it->value().fileDescriptor(); + fdArgs.insert(it.key(), QVariant::fromValue(QDBusUnixFileDescriptor(fd))); + } else { + blobArgs.insert(it.key(), it.value()); + } + } + QByteArray blob; + QDataStream stream(&blob, QIODevice::WriteOnly); + stream << blobArgs; + result.append(blob); + if (!fdArgs.isEmpty()) { + result.append(fdArgs); + } + + return result; +} + +QVariantMap unpackedArguments(const QVariantList &arguments) +{ + QVariantMap result; + + if (!arguments.isEmpty()) { + QByteArray blob = arguments.at(0).toByteArray(); + QDataStream s(&blob, QIODevice::ReadOnly); + + // Make sure we don't try restoring gui variants, in particular QImage/QPixmap/QIcon are super dangerous + // since they end up calling the image loaders and thus are a vector for crashing → executing code + auto origMetaTypeGuiHelper = qMetaTypeGuiHelper; + qMetaTypeGuiHelper = nullptr; + s >> result; + qMetaTypeGuiHelper = origMetaTypeGuiHelper; + + if (arguments.size() > 1) { + QVariantMap fdArgs; + const auto fdMapArg = arguments.at(1); + if (fdMapArg.canConvert()) { + const auto dbusArg = fdMapArg.value(); + dbusArg >> fdArgs; + } else { + fdArgs = fdMapArg.value(); + } + for (auto it = fdArgs.constBegin(); it != fdArgs.constEnd(); ++it) { + auto fd = it->value(); + if (fd.isValid()) { + result.insert(it.key(), QVariant::fromValue(UnixFileDescriptor(fd.fileDescriptor()))); + } + } + } + } + + return result; +} + +QVariantList packedReply(const ActionReply &reply) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s << reply.errorCode() << static_cast(reply.type()) << reply.errorDescription(); + + QVariantList result; + result << data << packedArguments(reply.data()); + + return result; +} + +ActionReply unpackReply(const QVariantList &packedData) +{ + ActionReply reply; + QVariantList data = packedData; + if (!data.isEmpty()) { + QDataStream s(data.takeFirst().toByteArray()); + + quint32 i; + uint errorCode; + QString errorDescription; + s >> errorCode >> i >> errorDescription; + reply.setError(errorCode); + reply.setType(static_cast(i)); + reply.setErrorDescription(errorDescription); + + if (!data.isEmpty()) { + reply.setData(unpackedArguments(data)); + } + } + return reply; +} + } // namespace Auth diff --git a/src/backends/dbus/org.kde.kf5auth.xml b/src/backends/dbus/org.kde.kf5auth.xml --- a/src/backends/dbus/org.kde.kf5auth.xml +++ b/src/backends/dbus/org.kde.kf5auth.xml @@ -4,17 +4,19 @@ - - + + + - + + diff --git a/src/backends/fakehelper/FakeHelperProxy.h b/src/backends/fakehelper/FakeHelperProxy.h --- a/src/backends/fakehelper/FakeHelperProxy.h +++ b/src/backends/fakehelper/FakeHelperProxy.h @@ -42,6 +42,7 @@ bool initHelper(const QString &name) override; void stopAction(const QString &action, const QString &helperID) override; void executeAction(const QString &action, const QString &helperID, const QVariantMap &arguments, int timeout = -1) override; + bool isUnixFileDescriptorPassingSupported() const override; }; } diff --git a/src/backends/fakehelper/FakeHelperProxy.cpp b/src/backends/fakehelper/FakeHelperProxy.cpp --- a/src/backends/fakehelper/FakeHelperProxy.cpp +++ b/src/backends/fakehelper/FakeHelperProxy.cpp @@ -79,5 +79,10 @@ emit actionPerformed(action, KAuth::ActionReply::NoSuchActionReply()); } +bool FakeHelperProxy::isUnixFileDescriptorPassingSupported() const +{ + return false; +} + } diff --git a/src/kauth.h b/src/kauth.h --- a/src/kauth.h +++ b/src/kauth.h @@ -25,5 +25,6 @@ #include "kauthactionreply.h" #include "kauthexecutejob.h" #include "kauthhelpersupport.h" +#include "kauthunixfiledescriptor.h" #endif diff --git a/src/kauthunixfiledescriptor.h b/src/kauthunixfiledescriptor.h new file mode 100644 --- /dev/null +++ b/src/kauthunixfiledescriptor.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2019 Alexander Volkov +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2.1 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . +*/ + +#ifndef UNIXFILEDESCRIPTOR_H +#define UNIXFILEDESCRIPTOR_H + +#include +#include + +#include + +namespace KAuth +{ + +class UnixFileDescriptorData; + +class KAUTH_EXPORT UnixFileDescriptor +{ +public: + UnixFileDescriptor(); + explicit UnixFileDescriptor(int fileDescriptor); + UnixFileDescriptor(const UnixFileDescriptor &other); + UnixFileDescriptor(UnixFileDescriptor &&other); + UnixFileDescriptor &operator=(const UnixFileDescriptor &other); + UnixFileDescriptor &operator=(UnixFileDescriptor &&other); + ~UnixFileDescriptor(); + + int fileDescriptor() const; + bool isValid() const; + + static bool isSupported(); + +private: + QSharedDataPointer d; +}; + +} // namespace Auth + +Q_DECLARE_METATYPE(KAuth::UnixFileDescriptor) + +#endif // UNIXFILEDESCRIPTOR_H diff --git a/src/kauthunixfiledescriptor.cpp b/src/kauthunixfiledescriptor.cpp new file mode 100644 --- /dev/null +++ b/src/kauthunixfiledescriptor.cpp @@ -0,0 +1,94 @@ +/* +* Copyright (C) 2019 Alexander Volkov +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2.1 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . +*/ + +#include "kauthunixfiledescriptor.h" +#include "BackendsManager.h" + +#include + +namespace KAuth +{ + +class UnixFileDescriptorData : public QSharedData +{ +public: + UnixFileDescriptorData() {} + UnixFileDescriptorData(int fd) : fd(fd) {} + ~UnixFileDescriptorData() {} + + QDBusUnixFileDescriptor fd; +}; + +UnixFileDescriptor::UnixFileDescriptor() + : d(new UnixFileDescriptorData()) +{ +} + +UnixFileDescriptor::UnixFileDescriptor(int fileDescriptor) + : d(new UnixFileDescriptorData(fileDescriptor)) +{ +} + +UnixFileDescriptor::UnixFileDescriptor(const UnixFileDescriptor &other) + : d(other.d) +{ +} + +UnixFileDescriptor::UnixFileDescriptor(UnixFileDescriptor &&other) + : d(std::move(other.d)) +{ +} + +UnixFileDescriptor &UnixFileDescriptor::operator=(const UnixFileDescriptor &other) +{ + if (this == &other) { + return *this; + } + + d = other.d; + return *this; +} + +UnixFileDescriptor &UnixFileDescriptor::operator=(UnixFileDescriptor &&other) +{ + d.swap(other.d); + return *this; +} + +UnixFileDescriptor::~UnixFileDescriptor() +{ +} + +int UnixFileDescriptor::fileDescriptor() const +{ + return d->fd.fileDescriptor(); +} + +bool UnixFileDescriptor::isValid() const +{ + return d->fd.isValid(); +} + +bool UnixFileDescriptor::isSupported() +{ + return QDBusUnixFileDescriptor::isSupported() && + BackendsManager::helperProxy()->isUnixFileDescriptorPassingSupported(); +} + +} // namespace Auth