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"