File indexing completed on 2024-05-05 16:05:15

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