keepassxc
389 строк · 14.8 Кб
1/*
2* Copyright (C) 2020 Aetf <aetf@unlimitedcode.works>
3*
4* This program is free software: you can redistribute it and/or modify
5* it under the terms of the GNU General Public License as published by
6* the Free Software Foundation, either version 2 or (at your option)
7* version 3 of the License.
8*
9* This program is distributed in the hope that it will be useful,
10* but WITHOUT ANY WARRANTY; without even the implied warranty of
11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12* GNU General Public License for more details.
13*
14* You should have received a copy of the GNU General Public License
15* along with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "fdosecrets/dbus/DBusObject.h"
19
20#include <QDBusMetaType>
21#include <QThread>
22#include <QtDBus>
23
24namespace FdoSecrets
25{
26QString camelToPascal(const QString& camel)
27{
28if (camel.isEmpty()) {
29return camel;
30}
31return camel.at(0).toUpper() + camel.mid(1);
32}
33
34bool prepareInputParams(const QVector<int>& inputTypes,
35const QVariantList& args,
36QVarLengthArray<void*, 10>& params,
37QVariantList& auxParams)
38{
39// prepare params
40for (int count = 0; count != inputTypes.size(); ++count) {
41const auto& id = inputTypes.at(count);
42const auto& arg = args.at(count);
43
44if (arg.userType() == id) {
45// shortcut for no conversion
46params.append(const_cast<void*>(arg.constData()));
47continue;
48}
49
50// we need at least one conversion, allocate a slot in auxParams
51auxParams.append(QVariant(id, nullptr));
52auto& out = auxParams.last();
53// first handle QDBusArgument to wire types
54if (arg.userType() == qMetaTypeId<QDBusArgument>()) {
55auto wireId = typeToWireType(id).dbusTypeId;
56out = QVariant(wireId, nullptr);
57
58const auto& in = arg.value<QDBusArgument>();
59if (!QDBusMetaType::demarshall(in, wireId, out.data())) {
60qDebug() << "Internal error: failed QDBusArgument conversion from" << arg << "to type"
61<< QMetaType::typeName(wireId) << wireId;
62return false;
63}
64} else {
65// make a copy to store the converted value
66out = arg;
67}
68// other conversions are handled here
69if (!out.convert(id)) {
70qDebug() << "Internal error: failed conversion from" << arg << "to type" << QMetaType::typeName(id)
71<< id;
72return false;
73}
74// good to go
75params.append(const_cast<void*>(out.constData()));
76}
77return true;
78}
79
80void DBusMgr::populateMethodCache(const QMetaObject& mo)
81{
82for (int i = mo.methodOffset(); i != mo.methodCount(); ++i) {
83auto mm = mo.method(i);
84
85// only register public Q_INVOKABLE methods
86if (mm.access() != QMetaMethod::Public || mm.methodType() != QMetaMethod::Method) {
87continue;
88}
89if (mm.returnType() != qMetaTypeId<DBusResult>()) {
90continue;
91}
92
93auto iface = mo.classInfo(mo.indexOfClassInfo("D-Bus Interface")).value();
94if (!iface) {
95continue;
96}
97
98// map from function name to dbus name
99auto member = camelToPascal(mm.name());
100// also "remove" => "Delete" due to c++ keyword restriction
101if (member == "Remove") {
102member = QStringLiteral("Delete");
103}
104auto cacheKey = QStringLiteral("%1.%2").arg(iface, member);
105
106// skip if we already have it
107auto it = m_cachedMethods.find(cacheKey);
108if (it != m_cachedMethods.end()) {
109continue;
110}
111
112MethodData md;
113md.isProperty = mm.tag() && mm.tag() == QStringLiteral("DBUS_PROPERTY");
114md.slotIdx = mm.methodIndex();
115
116bool valid = true;
117// assumes output params (reference parameter) all follows input params
118bool outputBegin = false;
119for (const auto& paramType : mm.parameterTypes()) {
120auto id = QMetaType::type(paramType);
121
122// handle the first optional calling client param
123if (id == qMetaTypeId<DBusClientPtr>()) {
124md.needsCallingClient = true;
125continue;
126}
127
128// handle output types
129if (paramType.endsWith('&')) {
130outputBegin = true;
131id = QMetaType::type(paramType.left(paramType.length() - 1));
132md.outputTypes.append(id);
133auto paramData = typeToWireType(id);
134if (paramData.signature.isEmpty()) {
135qDebug() << "Internal error: unhandled new output type for dbus signature" << paramType;
136valid = false;
137break;
138}
139md.outputTargetTypes.append(paramData.dbusTypeId);
140continue;
141}
142
143// handle input types
144if (outputBegin) {
145qDebug() << "Internal error: invalid method parameter order, no input parameter after output ones"
146<< mm.name();
147valid = false;
148break;
149}
150auto sig = typeToWireType(id).signature;
151if (sig.isEmpty()) {
152qDebug() << "Internal error: unhandled new parameter type for dbus signature" << paramType;
153valid = false;
154break;
155}
156md.inputTypes.append(id);
157md.signature += sig;
158}
159if (valid) {
160m_cachedMethods.insert(cacheKey, md);
161}
162}
163}
164
165bool DBusMgr::handleMessage(const QDBusMessage& message, const QDBusConnection&)
166{
167// save a mutable copy of the message, as we may modify it to unify property access
168// and method call
169RequestedMethod req{
170message.interface(),
171message.member(),
172message.signature(),
173message.arguments(),
174RequestType::Method,
175};
176
177if (req.interface == "org.freedesktop.DBus.Introspectable") {
178// introspection can be handled by Qt, just return false
179return false;
180} else if (req.interface == "org.freedesktop.DBus.Properties") {
181// but we need to handle properties ourselves like regular functions
182if (!rewriteRequestForProperty(req)) {
183// invalid message
184qDebug() << "Invalid message" << message;
185return false;
186}
187}
188
189// who's calling?
190const auto& client = findClient(message.service());
191if (!client) {
192// the client already died
193return false;
194}
195
196// activate the target object
197return activateObject(client, message.path(), req, message);
198}
199
200bool DBusMgr::rewriteRequestForProperty(RequestedMethod& req)
201{
202if (req.member == "Set" && req.signature == "ssv") {
203// convert to normal method call: SetName
204req.interface = req.args.at(0).toString();
205req.member = req.member + req.args.at(1).toString();
206// unwrap the QDBusVariant and expose the inner signature
207auto arg = req.args.last().value<QDBusVariant>().variant();
208req.args = {arg};
209if (arg.userType() == qMetaTypeId<QDBusArgument>()) {
210req.signature = arg.value<QDBusArgument>().currentSignature();
211} else if (arg.userType() == QMetaType::QString) {
212req.signature = "s";
213} else {
214qDebug() << "Unhandled SetProperty value type" << QMetaType::typeName(arg.userType()) << arg.userType();
215return false;
216}
217} else if (req.member == "Get" && req.signature == "ss") {
218// convert to normal method call: Name
219req.interface = req.args.at(0).toString();
220req.member = req.args.at(1).toString();
221req.signature = "";
222req.args = {};
223req.type = RequestType::PropertyGet;
224} else if (req.member == "GetAll" && req.signature == "s") {
225// special handled in activateObject
226req.interface = req.args.at(0).toString();
227req.member = "";
228req.signature = "";
229req.args = {};
230req.type = RequestType::PropertyGetAll;
231} else {
232return false;
233}
234return true;
235}
236
237bool DBusMgr::activateObject(const DBusClientPtr& client,
238const QString& path,
239const RequestedMethod& req,
240const QDBusMessage& msg)
241{
242auto obj = m_objects.value(path, nullptr);
243if (!obj) {
244qDebug() << "DBusMgr::handleMessage with unknown path" << msg;
245return false;
246}
247Q_ASSERT_X(QThread::currentThread() == obj->thread(),
248"QDBusConnection: internal threading error",
249"function called for an object that is in another thread!!");
250
251auto mo = obj->metaObject();
252// either interface matches, or interface is empty if req is property get all
253QString interface = mo->classInfo(mo->indexOfClassInfo("D-Bus Interface")).value();
254if (req.interface != interface && !(req.type == RequestType::PropertyGetAll && req.interface.isEmpty())) {
255qDebug() << "DBusMgr::handleMessage with mismatch interface" << msg;
256return false;
257}
258
259// special handle of property getall
260if (req.type == RequestType::PropertyGetAll) {
261return objectPropertyGetAll(client, obj, interface, msg);
262}
263
264// find the slot to call
265auto cacheKey = QStringLiteral("%1.%2").arg(req.interface, req.member);
266auto it = m_cachedMethods.find(cacheKey);
267if (it == m_cachedMethods.end()) {
268qDebug() << "DBusMgr::handleMessage with nonexisting method" << cacheKey;
269return false;
270}
271
272// requested signature is verified by Qt to match the content of arguments,
273// but this list of arguments itself is untrusted
274if (it->signature != req.signature || it->inputTypes.size() != req.args.size()) {
275qDebug() << "Message signature does not match, expected" << it->signature << it->inputTypes.size() << "got"
276<< req.signature << req.args.size();
277return false;
278}
279
280DBusResult ret;
281QVariantList outputArgs;
282if (!deliverMethod(client, obj, *it, req.args, ret, outputArgs)) {
283qDebug() << "Failed to deliver method" << msg;
284return sendDBus(msg.createErrorReply(QDBusError::InternalError, tr("Failed to deliver message")));
285}
286
287if (!ret.ok()) {
288return sendDBus(msg.createErrorReply(ret, ""));
289}
290if (req.type == RequestType::PropertyGet) {
291// property get need the reply wrapped in QDBusVariant
292outputArgs[0] = QVariant::fromValue(QDBusVariant(outputArgs.first()));
293}
294return sendDBus(msg.createReply(outputArgs));
295}
296
297bool DBusMgr::objectPropertyGetAll(const DBusClientPtr& client,
298DBusObject* obj,
299const QString& interface,
300const QDBusMessage& msg)
301{
302QVariantMap result;
303
304// prefix match the cacheKey
305auto prefix = interface + ".";
306for (auto it = m_cachedMethods.constBegin(); it != m_cachedMethods.constEnd(); ++it) {
307if (!it.key().startsWith(prefix)) {
308continue;
309}
310if (!it.value().isProperty) {
311continue;
312}
313auto name = it.key().mid(prefix.size());
314
315DBusResult ret;
316QVariantList outputArgs;
317if (!deliverMethod(client, obj, it.value(), {}, ret, outputArgs)) {
318// ignore any error per spec
319continue;
320}
321if (ret.err()) {
322// ignore any error per spec
323continue;
324}
325Q_ASSERT(outputArgs.size() == 1);
326
327result.insert(name, outputArgs.first());
328}
329
330return sendDBus(msg.createReply(QVariantList{result}));
331}
332
333bool DBusMgr::deliverMethod(const DBusClientPtr& client,
334DBusObject* obj,
335const MethodData& method,
336const QVariantList& args,
337DBusResult& ret,
338QVariantList& outputArgs)
339{
340QVarLengthArray<void*, 10> params;
341QVariantList auxParams;
342
343// the first one is for return type
344params.append(&ret);
345
346if (method.needsCallingClient) {
347auxParams.append(QVariant::fromValue(client));
348params.append(const_cast<void*>(auxParams.last().constData()));
349}
350
351// prepare input
352if (!prepareInputParams(method.inputTypes, args, params, auxParams)) {
353qDebug() << "Failed to prepare input params";
354return false;
355}
356
357// prepare output args
358outputArgs.reserve(outputArgs.size() + method.outputTypes.size());
359for (const auto& outputType : asConst(method.outputTypes)) {
360outputArgs.append(QVariant(outputType, nullptr));
361params.append(const_cast<void*>(outputArgs.last().constData()));
362}
363
364// call it
365bool fail = obj->qt_metacall(QMetaObject::InvokeMetaMethod, method.slotIdx, params.data()) >= 0;
366if (fail) {
367// generate internal error
368qWarning() << "Internal error: Failed to deliver message";
369return false;
370}
371
372if (!ret.ok()) {
373// error reply
374return true;
375}
376
377// output args need to be converted before they can be directly sent out:
378for (int i = 0; i != outputArgs.size(); ++i) {
379auto& outputArg = outputArgs[i];
380if (!outputArg.convert(method.outputTargetTypes.at(i))) {
381qWarning() << "Internal error: Failed to convert message output to type"
382<< method.outputTargetTypes.at(i);
383return false;
384}
385}
386
387return true;
388}
389} // namespace FdoSecrets
390