keepassxc

Форк
0
/
DBusDispatch.cpp 
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

24
namespace FdoSecrets
25
{
26
    QString camelToPascal(const QString& camel)
27
    {
28
        if (camel.isEmpty()) {
29
            return camel;
30
        }
31
        return camel.at(0).toUpper() + camel.mid(1);
32
    }
33

34
    bool prepareInputParams(const QVector<int>& inputTypes,
35
                            const QVariantList& args,
36
                            QVarLengthArray<void*, 10>& params,
37
                            QVariantList& auxParams)
38
    {
39
        // prepare params
40
        for (int count = 0; count != inputTypes.size(); ++count) {
41
            const auto& id = inputTypes.at(count);
42
            const auto& arg = args.at(count);
43

44
            if (arg.userType() == id) {
45
                // shortcut for no conversion
46
                params.append(const_cast<void*>(arg.constData()));
47
                continue;
48
            }
49

50
            // we need at least one conversion, allocate a slot in auxParams
51
            auxParams.append(QVariant(id, nullptr));
52
            auto& out = auxParams.last();
53
            // first handle QDBusArgument to wire types
54
            if (arg.userType() == qMetaTypeId<QDBusArgument>()) {
55
                auto wireId = typeToWireType(id).dbusTypeId;
56
                out = QVariant(wireId, nullptr);
57

58
                const auto& in = arg.value<QDBusArgument>();
59
                if (!QDBusMetaType::demarshall(in, wireId, out.data())) {
60
                    qDebug() << "Internal error: failed QDBusArgument conversion from" << arg << "to type"
61
                             << QMetaType::typeName(wireId) << wireId;
62
                    return false;
63
                }
64
            } else {
65
                // make a copy to store the converted value
66
                out = arg;
67
            }
68
            // other conversions are handled here
69
            if (!out.convert(id)) {
70
                qDebug() << "Internal error: failed conversion from" << arg << "to type" << QMetaType::typeName(id)
71
                         << id;
72
                return false;
73
            }
74
            // good to go
75
            params.append(const_cast<void*>(out.constData()));
76
        }
77
        return true;
78
    }
79

80
    void DBusMgr::populateMethodCache(const QMetaObject& mo)
81
    {
82
        for (int i = mo.methodOffset(); i != mo.methodCount(); ++i) {
83
            auto mm = mo.method(i);
84

85
            // only register public Q_INVOKABLE methods
86
            if (mm.access() != QMetaMethod::Public || mm.methodType() != QMetaMethod::Method) {
87
                continue;
88
            }
89
            if (mm.returnType() != qMetaTypeId<DBusResult>()) {
90
                continue;
91
            }
92

93
            auto iface = mo.classInfo(mo.indexOfClassInfo("D-Bus Interface")).value();
94
            if (!iface) {
95
                continue;
96
            }
97

98
            // map from function name to dbus name
99
            auto member = camelToPascal(mm.name());
100
            // also "remove" => "Delete" due to c++ keyword restriction
101
            if (member == "Remove") {
102
                member = QStringLiteral("Delete");
103
            }
104
            auto cacheKey = QStringLiteral("%1.%2").arg(iface, member);
105

106
            // skip if we already have it
107
            auto it = m_cachedMethods.find(cacheKey);
108
            if (it != m_cachedMethods.end()) {
109
                continue;
110
            }
111

112
            MethodData md;
113
            md.isProperty = mm.tag() && mm.tag() == QStringLiteral("DBUS_PROPERTY");
114
            md.slotIdx = mm.methodIndex();
115

116
            bool valid = true;
117
            // assumes output params (reference parameter) all follows input params
118
            bool outputBegin = false;
119
            for (const auto& paramType : mm.parameterTypes()) {
120
                auto id = QMetaType::type(paramType);
121

122
                // handle the first optional calling client param
123
                if (id == qMetaTypeId<DBusClientPtr>()) {
124
                    md.needsCallingClient = true;
125
                    continue;
126
                }
127

128
                // handle output types
129
                if (paramType.endsWith('&')) {
130
                    outputBegin = true;
131
                    id = QMetaType::type(paramType.left(paramType.length() - 1));
132
                    md.outputTypes.append(id);
133
                    auto paramData = typeToWireType(id);
134
                    if (paramData.signature.isEmpty()) {
135
                        qDebug() << "Internal error: unhandled new output type for dbus signature" << paramType;
136
                        valid = false;
137
                        break;
138
                    }
139
                    md.outputTargetTypes.append(paramData.dbusTypeId);
140
                    continue;
141
                }
142

143
                // handle input types
144
                if (outputBegin) {
145
                    qDebug() << "Internal error: invalid method parameter order, no input parameter after output ones"
146
                             << mm.name();
147
                    valid = false;
148
                    break;
149
                }
150
                auto sig = typeToWireType(id).signature;
151
                if (sig.isEmpty()) {
152
                    qDebug() << "Internal error: unhandled new parameter type for dbus signature" << paramType;
153
                    valid = false;
154
                    break;
155
                }
156
                md.inputTypes.append(id);
157
                md.signature += sig;
158
            }
159
            if (valid) {
160
                m_cachedMethods.insert(cacheKey, md);
161
            }
162
        }
163
    }
164

165
    bool 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
169
        RequestedMethod req{
170
            message.interface(),
171
            message.member(),
172
            message.signature(),
173
            message.arguments(),
174
            RequestType::Method,
175
        };
176

177
        if (req.interface == "org.freedesktop.DBus.Introspectable") {
178
            // introspection can be handled by Qt, just return false
179
            return false;
180
        } else if (req.interface == "org.freedesktop.DBus.Properties") {
181
            // but we need to handle properties ourselves like regular functions
182
            if (!rewriteRequestForProperty(req)) {
183
                // invalid message
184
                qDebug() << "Invalid message" << message;
185
                return false;
186
            }
187
        }
188

189
        // who's calling?
190
        const auto& client = findClient(message.service());
191
        if (!client) {
192
            // the client already died
193
            return false;
194
        }
195

196
        // activate the target object
197
        return activateObject(client, message.path(), req, message);
198
    }
199

200
    bool DBusMgr::rewriteRequestForProperty(RequestedMethod& req)
201
    {
202
        if (req.member == "Set" && req.signature == "ssv") {
203
            // convert to normal method call: SetName
204
            req.interface = req.args.at(0).toString();
205
            req.member = req.member + req.args.at(1).toString();
206
            // unwrap the QDBusVariant and expose the inner signature
207
            auto arg = req.args.last().value<QDBusVariant>().variant();
208
            req.args = {arg};
209
            if (arg.userType() == qMetaTypeId<QDBusArgument>()) {
210
                req.signature = arg.value<QDBusArgument>().currentSignature();
211
            } else if (arg.userType() == QMetaType::QString) {
212
                req.signature = "s";
213
            } else {
214
                qDebug() << "Unhandled SetProperty value type" << QMetaType::typeName(arg.userType()) << arg.userType();
215
                return false;
216
            }
217
        } else if (req.member == "Get" && req.signature == "ss") {
218
            // convert to normal method call: Name
219
            req.interface = req.args.at(0).toString();
220
            req.member = req.args.at(1).toString();
221
            req.signature = "";
222
            req.args = {};
223
            req.type = RequestType::PropertyGet;
224
        } else if (req.member == "GetAll" && req.signature == "s") {
225
            // special handled in activateObject
226
            req.interface = req.args.at(0).toString();
227
            req.member = "";
228
            req.signature = "";
229
            req.args = {};
230
            req.type = RequestType::PropertyGetAll;
231
        } else {
232
            return false;
233
        }
234
        return true;
235
    }
236

237
    bool DBusMgr::activateObject(const DBusClientPtr& client,
238
                                 const QString& path,
239
                                 const RequestedMethod& req,
240
                                 const QDBusMessage& msg)
241
    {
242
        auto obj = m_objects.value(path, nullptr);
243
        if (!obj) {
244
            qDebug() << "DBusMgr::handleMessage with unknown path" << msg;
245
            return false;
246
        }
247
        Q_ASSERT_X(QThread::currentThread() == obj->thread(),
248
                   "QDBusConnection: internal threading error",
249
                   "function called for an object that is in another thread!!");
250

251
        auto mo = obj->metaObject();
252
        // either interface matches, or interface is empty if req is property get all
253
        QString interface = mo->classInfo(mo->indexOfClassInfo("D-Bus Interface")).value();
254
        if (req.interface != interface && !(req.type == RequestType::PropertyGetAll && req.interface.isEmpty())) {
255
            qDebug() << "DBusMgr::handleMessage with mismatch interface" << msg;
256
            return false;
257
        }
258

259
        // special handle of property getall
260
        if (req.type == RequestType::PropertyGetAll) {
261
            return objectPropertyGetAll(client, obj, interface, msg);
262
        }
263

264
        // find the slot to call
265
        auto cacheKey = QStringLiteral("%1.%2").arg(req.interface, req.member);
266
        auto it = m_cachedMethods.find(cacheKey);
267
        if (it == m_cachedMethods.end()) {
268
            qDebug() << "DBusMgr::handleMessage with nonexisting method" << cacheKey;
269
            return 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
274
        if (it->signature != req.signature || it->inputTypes.size() != req.args.size()) {
275
            qDebug() << "Message signature does not match, expected" << it->signature << it->inputTypes.size() << "got"
276
                     << req.signature << req.args.size();
277
            return false;
278
        }
279

280
        DBusResult ret;
281
        QVariantList outputArgs;
282
        if (!deliverMethod(client, obj, *it, req.args, ret, outputArgs)) {
283
            qDebug() << "Failed to deliver method" << msg;
284
            return sendDBus(msg.createErrorReply(QDBusError::InternalError, tr("Failed to deliver message")));
285
        }
286

287
        if (!ret.ok()) {
288
            return sendDBus(msg.createErrorReply(ret, ""));
289
        }
290
        if (req.type == RequestType::PropertyGet) {
291
            // property get need the reply wrapped in QDBusVariant
292
            outputArgs[0] = QVariant::fromValue(QDBusVariant(outputArgs.first()));
293
        }
294
        return sendDBus(msg.createReply(outputArgs));
295
    }
296

297
    bool DBusMgr::objectPropertyGetAll(const DBusClientPtr& client,
298
                                       DBusObject* obj,
299
                                       const QString& interface,
300
                                       const QDBusMessage& msg)
301
    {
302
        QVariantMap result;
303

304
        // prefix match the cacheKey
305
        auto prefix = interface + ".";
306
        for (auto it = m_cachedMethods.constBegin(); it != m_cachedMethods.constEnd(); ++it) {
307
            if (!it.key().startsWith(prefix)) {
308
                continue;
309
            }
310
            if (!it.value().isProperty) {
311
                continue;
312
            }
313
            auto name = it.key().mid(prefix.size());
314

315
            DBusResult ret;
316
            QVariantList outputArgs;
317
            if (!deliverMethod(client, obj, it.value(), {}, ret, outputArgs)) {
318
                // ignore any error per spec
319
                continue;
320
            }
321
            if (ret.err()) {
322
                // ignore any error per spec
323
                continue;
324
            }
325
            Q_ASSERT(outputArgs.size() == 1);
326

327
            result.insert(name, outputArgs.first());
328
        }
329

330
        return sendDBus(msg.createReply(QVariantList{result}));
331
    }
332

333
    bool DBusMgr::deliverMethod(const DBusClientPtr& client,
334
                                DBusObject* obj,
335
                                const MethodData& method,
336
                                const QVariantList& args,
337
                                DBusResult& ret,
338
                                QVariantList& outputArgs)
339
    {
340
        QVarLengthArray<void*, 10> params;
341
        QVariantList auxParams;
342

343
        // the first one is for return type
344
        params.append(&ret);
345

346
        if (method.needsCallingClient) {
347
            auxParams.append(QVariant::fromValue(client));
348
            params.append(const_cast<void*>(auxParams.last().constData()));
349
        }
350

351
        // prepare input
352
        if (!prepareInputParams(method.inputTypes, args, params, auxParams)) {
353
            qDebug() << "Failed to prepare input params";
354
            return false;
355
        }
356

357
        // prepare output args
358
        outputArgs.reserve(outputArgs.size() + method.outputTypes.size());
359
        for (const auto& outputType : asConst(method.outputTypes)) {
360
            outputArgs.append(QVariant(outputType, nullptr));
361
            params.append(const_cast<void*>(outputArgs.last().constData()));
362
        }
363

364
        // call it
365
        bool fail = obj->qt_metacall(QMetaObject::InvokeMetaMethod, method.slotIdx, params.data()) >= 0;
366
        if (fail) {
367
            // generate internal error
368
            qWarning() << "Internal error: Failed to deliver message";
369
            return false;
370
        }
371

372
        if (!ret.ok()) {
373
            // error reply
374
            return true;
375
        }
376

377
        // output args need to be converted before they can be directly sent out:
378
        for (int i = 0; i != outputArgs.size(); ++i) {
379
            auto& outputArg = outputArgs[i];
380
            if (!outputArg.convert(method.outputTargetTypes.at(i))) {
381
                qWarning() << "Internal error: Failed to convert message output to type"
382
                           << method.outputTargetTypes.at(i);
383
                return false;
384
            }
385
        }
386

387
        return true;
388
    }
389
} // namespace FdoSecrets
390

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.