File indexing completed on 2024-04-28 03:52:38

0001 /*
0002     SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
0003     SPDX-FileCopyrightText: 2009-2010 Dario Freddi <drf@kde.org>
0004     SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "DBusHelperProxy.h"
0010 #include "BackendsManager.h"
0011 #include "kauthdebug.h"
0012 #include "kf6authadaptor.h"
0013 
0014 #include <QDBusConnectionInterface>
0015 #include <QDBusMessage>
0016 #include <QMap>
0017 #include <QMetaMethod>
0018 #include <QObject>
0019 #include <QTimer>
0020 #include <qplugin.h>
0021 
0022 extern Q_CORE_EXPORT const QtPrivate::QMetaTypeInterface *qMetaTypeGuiHelper;
0023 
0024 namespace KAuth
0025 {
0026 static void debugMessageReceived(int t, const QString &message);
0027 
0028 DBusHelperProxy::DBusHelperProxy()
0029     : responder(nullptr)
0030     , m_stopRequest(false)
0031     , m_busConnection(QDBusConnection::systemBus())
0032 {
0033 }
0034 
0035 DBusHelperProxy::DBusHelperProxy(const QDBusConnection &busConnection)
0036     : responder(nullptr)
0037     , m_stopRequest(false)
0038     , m_busConnection(busConnection)
0039 {
0040 }
0041 
0042 DBusHelperProxy::~DBusHelperProxy()
0043 {
0044 }
0045 
0046 void DBusHelperProxy::stopAction(const QString &action, const QString &helperID)
0047 {
0048     QDBusMessage message;
0049     message = QDBusMessage::createMethodCall(helperID, QLatin1String("/"), QLatin1String("org.kde.kf6auth"), QLatin1String("stopAction"));
0050 
0051     QList<QVariant> args;
0052     args << action;
0053     message.setArguments(args);
0054 
0055     m_busConnection.asyncCall(message);
0056 }
0057 
0058 void DBusHelperProxy::executeAction(const QString &action, const QString &helperID, const DetailsMap &details, const QVariantMap &arguments, int timeout)
0059 {
0060     QByteArray blob;
0061     {
0062         QDataStream stream(&blob, QIODevice::WriteOnly);
0063         stream << arguments;
0064     }
0065 
0066     // on unit tests we won't have a service, but the service will already be running
0067     const auto reply = m_busConnection.interface()->startService(helperID);
0068     if (!reply.isValid() && !m_busConnection.interface()->isServiceRegistered(helperID)) {
0069         ActionReply errorReply = ActionReply::DBusErrorReply();
0070         errorReply.setErrorDescription(tr("DBus Backend error: service start %1 failed: %2").arg(helperID, reply.error().message()));
0071         Q_EMIT actionPerformed(action, errorReply);
0072         return;
0073     }
0074 
0075     const bool connected = m_busConnection.connect(helperID,
0076                                                    QLatin1String("/"),
0077                                                    QLatin1String("org.kde.kf6auth"),
0078                                                    QLatin1String("remoteSignal"),
0079                                                    this,
0080                                                    SLOT(remoteSignalReceived(int, QString, QByteArray)));
0081 
0082     // if already connected reply will be false but we won't have an error or a reason to fail
0083     if (!connected && m_busConnection.lastError().isValid()) {
0084         ActionReply errorReply = ActionReply::DBusErrorReply();
0085         errorReply.setErrorDescription(tr("DBus Backend error: connection to helper failed. %1\n(application: %2 helper: %3)")
0086                                            .arg(m_busConnection.lastError().message(), qApp->applicationName(), helperID));
0087         Q_EMIT actionPerformed(action, errorReply);
0088         return;
0089     }
0090 
0091     QDBusMessage message;
0092     message = QDBusMessage::createMethodCall(helperID, QLatin1String("/"), QLatin1String("org.kde.kf6auth"), QLatin1String("performAction"));
0093 
0094     QList<QVariant> args;
0095     args << action << BackendsManager::authBackend()->callerID() << BackendsManager::authBackend()->backendDetails(details) << blob;
0096     message.setArguments(args);
0097 
0098     m_actionsInProgress.push_back(action);
0099 
0100     QDBusPendingCall pendingCall = m_busConnection.asyncCall(message, timeout);
0101 
0102     auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
0103 
0104     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, action, args, message, watcher, timeout]() mutable {
0105         watcher->deleteLater();
0106 
0107         QDBusMessage reply = watcher->reply();
0108 
0109         if (reply.type() == QDBusMessage::ErrorMessage) {
0110             if (watcher->error().type() == QDBusError::InvalidArgs) {
0111                 // For backwards compatibility if helper binary was built with older KAuth version.
0112                 args.removeAt(args.count() - 2); // remove backend details
0113                 message.setArguments(args);
0114                 reply = m_busConnection.call(message, QDBus::Block, timeout);
0115                 if (reply.type() != QDBusMessage::ErrorMessage) {
0116                     return;
0117                 }
0118             }
0119             ActionReply r = ActionReply::DBusErrorReply();
0120             r.setErrorDescription(tr("DBus Backend error: could not contact the helper. "
0121                                      "Connection error: %1. Message error: %2")
0122                                       .arg(reply.errorMessage(), m_busConnection.lastError().message()));
0123             qCWarning(KAUTH) << reply.errorMessage();
0124 
0125             Q_EMIT actionPerformed(action, r);
0126         }
0127     });
0128 }
0129 
0130 bool DBusHelperProxy::initHelper(const QString &name)
0131 {
0132     new Kf6authAdaptor(this);
0133 
0134     if (!m_busConnection.registerService(name)) {
0135         qCWarning(KAUTH) << "Error registering helper DBus service" << name << m_busConnection.lastError().message();
0136         return false;
0137     }
0138 
0139     if (!m_busConnection.registerObject(QLatin1String("/"), this)) {
0140         qCWarning(KAUTH) << "Error registering helper DBus object:" << m_busConnection.lastError().message();
0141         return false;
0142     }
0143 
0144     m_name = name;
0145 
0146     return true;
0147 }
0148 
0149 void DBusHelperProxy::setHelperResponder(QObject *o)
0150 {
0151     responder = o;
0152 }
0153 
0154 void DBusHelperProxy::remoteSignalReceived(int t, const QString &action, QByteArray blob)
0155 {
0156     SignalType type = static_cast<SignalType>(t);
0157     QDataStream stream(&blob, QIODevice::ReadOnly);
0158 
0159     if (type == ActionStarted) {
0160         Q_EMIT actionStarted(action);
0161     } else if (type == ActionPerformed) {
0162         ActionReply reply = ActionReply::deserialize(blob);
0163 
0164         m_actionsInProgress.removeOne(action);
0165         Q_EMIT actionPerformed(action, reply);
0166     } else if (type == DebugMessage) {
0167         int level;
0168         QString message;
0169 
0170         stream >> level >> message;
0171 
0172         debugMessageReceived(level, message);
0173     } else if (type == ProgressStepIndicator) {
0174         int step;
0175         stream >> step;
0176 
0177         Q_EMIT progressStep(action, step);
0178     } else if (type == ProgressStepData) {
0179         QVariantMap data;
0180         stream >> data;
0181         Q_EMIT progressStepData(action, data);
0182     }
0183 }
0184 
0185 void DBusHelperProxy::stopAction(const QString &action)
0186 {
0187     Q_UNUSED(action)
0188     //#warning FIXME: The stop request should be action-specific rather than global
0189     m_stopRequest = true;
0190 }
0191 
0192 bool DBusHelperProxy::hasToStopAction()
0193 {
0194     QEventLoop loop;
0195     loop.processEvents(QEventLoop::AllEvents);
0196 
0197     return m_stopRequest;
0198 }
0199 
0200 bool DBusHelperProxy::isCallerAuthorized(const QString &action, const QByteArray &callerID, const QVariantMap &details)
0201 {
0202     Q_UNUSED(callerID); // this only exists for the benefit of the mac backend. We obtain our callerID from dbus!
0203     return BackendsManager::authBackend()->isCallerAuthorized(action, message().service().toUtf8(), details);
0204 }
0205 
0206 QByteArray DBusHelperProxy::performAction(const QString &action, const QByteArray &callerID, const QVariantMap &details, QByteArray arguments)
0207 {
0208     if (!responder) {
0209         return ActionReply::NoResponderReply().serialized();
0210     }
0211 
0212     if (!m_currentAction.isEmpty()) {
0213         return ActionReply::HelperBusyReply().serialized();
0214     }
0215 
0216     // Make sure we don't try restoring gui variants, in particular QImage/QPixmap/QIcon are super dangerous
0217     // since they end up calling the image loaders and thus are a vector for crashing → executing code
0218     auto origMetaTypeGuiHelper = qMetaTypeGuiHelper;
0219     qMetaTypeGuiHelper = nullptr;
0220 
0221     QVariantMap args;
0222     QDataStream s(&arguments, QIODevice::ReadOnly);
0223     s >> args;
0224 
0225     qMetaTypeGuiHelper = origMetaTypeGuiHelper;
0226 
0227     m_currentAction = action;
0228     Q_EMIT remoteSignal(ActionStarted, action, QByteArray());
0229     QEventLoop e;
0230     e.processEvents(QEventLoop::AllEvents);
0231 
0232     ActionReply retVal;
0233 
0234     QTimer *timer = responder->property("__KAuth_Helper_Shutdown_Timer").value<QTimer *>();
0235     timer->stop();
0236 
0237     if (isCallerAuthorized(action, callerID, details)) {
0238         QString slotname = action;
0239         if (slotname.startsWith(m_name + QLatin1Char('.'))) {
0240             slotname = slotname.right(slotname.length() - m_name.length() - 1);
0241         }
0242 
0243         slotname.replace(QLatin1Char('.'), QLatin1Char('_'));
0244 
0245         // For legacy reasons we could be dealing with ActionReply types (i.e.
0246         // `using namespace KAuth`). Since Qt type names are verbatim this would
0247         // mismatch a return type that is called 'KAuth::ActionReply' and
0248         // vice versa. This effectively required client code to always 'use' the
0249         // namespace as otherwise we'd not be able to call into it.
0250         // To support both scenarios we now dynamically determine what kind of return type
0251         // we deal with and call Q_RETURN_ARG either with or without namespace.
0252         const auto metaObj = responder->metaObject();
0253         const QString slotSignature(slotname + QStringLiteral("(QVariantMap)"));
0254         const QMetaMethod method = metaObj->method(metaObj->indexOfMethod(qPrintable(slotSignature)));
0255         if (method.isValid()) {
0256             const auto needle = "KAuth::";
0257             bool success = false;
0258             if (strncmp(needle, method.typeName(), strlen(needle)) == 0) {
0259                 success = method.invoke(responder, Qt::DirectConnection, Q_RETURN_ARG(KAuth::ActionReply, retVal), Q_ARG(QVariantMap, args));
0260             } else {
0261                 success = method.invoke(responder, Qt::DirectConnection, Q_RETURN_ARG(ActionReply, retVal), Q_ARG(QVariantMap, args));
0262             }
0263             if (!success) {
0264                 retVal = ActionReply::NoSuchActionReply();
0265             }
0266         } else {
0267             retVal = ActionReply::NoSuchActionReply();
0268         }
0269     } else {
0270         retVal = ActionReply::AuthorizationDeniedReply();
0271     }
0272 
0273     timer->start();
0274 
0275     Q_EMIT remoteSignal(ActionPerformed, action, retVal.serialized());
0276     e.processEvents(QEventLoop::AllEvents);
0277     m_currentAction.clear();
0278     m_stopRequest = false;
0279 
0280     return retVal.serialized();
0281 }
0282 
0283 void DBusHelperProxy::sendDebugMessage(int level, const char *msg)
0284 {
0285     QByteArray blob;
0286     QDataStream stream(&blob, QIODevice::WriteOnly);
0287 
0288     stream << level << QString::fromLocal8Bit(msg);
0289 
0290     Q_EMIT remoteSignal(DebugMessage, m_currentAction, blob);
0291 }
0292 
0293 void DBusHelperProxy::sendProgressStep(int step)
0294 {
0295     QByteArray blob;
0296     QDataStream stream(&blob, QIODevice::WriteOnly);
0297 
0298     stream << step;
0299 
0300     Q_EMIT remoteSignal(ProgressStepIndicator, m_currentAction, blob);
0301 }
0302 
0303 void DBusHelperProxy::sendProgressStepData(const QVariantMap &data)
0304 {
0305     QByteArray blob;
0306     QDataStream stream(&blob, QIODevice::WriteOnly);
0307 
0308     stream << data;
0309 
0310     Q_EMIT remoteSignal(ProgressStepData, m_currentAction, blob);
0311 }
0312 
0313 void debugMessageReceived(int t, const QString &message)
0314 {
0315     QtMsgType type = static_cast<QtMsgType>(t);
0316     switch (type) {
0317     case QtDebugMsg:
0318         qDebug("Debug message from helper: %s", message.toLatin1().data());
0319         break;
0320     case QtInfoMsg:
0321         qInfo("Info message from helper: %s", message.toLatin1().data());
0322         break;
0323     case QtWarningMsg:
0324         qWarning("Warning from helper: %s", message.toLatin1().data());
0325         break;
0326     case QtCriticalMsg:
0327         qCritical("Critical warning from helper: %s", message.toLatin1().data());
0328         break;
0329     case QtFatalMsg:
0330         qFatal("Fatal error from helper: %s", message.toLatin1().data());
0331         break;
0332     }
0333 }
0334 
0335 int DBusHelperProxy::callerUid() const
0336 {
0337     QDBusConnectionInterface *iface = connection().interface();
0338     if (!iface) {
0339         return -1;
0340     }
0341     return iface->serviceUid(message().service());
0342 }
0343 
0344 } // namespace KAuth
0345 
0346 #include "moc_DBusHelperProxy.cpp"