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"