1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the QtSystems module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL21$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 or version 3 as published by the Free |
20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and |
21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the |
22 | ** following information to ensure the GNU Lesser General Public License |
23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and |
24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
25 | ** |
26 | ** As a special exception, The Qt Company gives you certain additional |
27 | ** rights. These rights are described in The Qt Company LGPL Exception |
28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
29 | ** |
30 | ** $QT_END_LICENSE$ |
31 | ** |
32 | ****************************************************************************/ |
33 | |
34 | #include "qremoteserviceregister_p.h" |
35 | #include "qremoteserviceregister_dbus_p.h" |
36 | #include "qserviceclientcredentials_p.h" |
37 | |
38 | #include <QDataStream> |
39 | #include <QTimer> |
40 | |
41 | |
42 | QT_BEGIN_NAMESPACE |
43 | |
44 | struct dbus_creds |
45 | { |
46 | int pid; |
47 | int uid; |
48 | }; |
49 | |
50 | class DBusEndPoint : public QServiceIpcEndPoint |
51 | { |
52 | Q_OBJECT |
53 | |
54 | public: |
55 | DBusEndPoint(QDBusInterface* iface, int type, QObject* parent = 0) |
56 | : QServiceIpcEndPoint(parent), interface(iface), endType(type) |
57 | { |
58 | Q_ASSERT(interface); |
59 | interface->setParent(this); |
60 | |
61 | connect(sender: interface, SIGNAL(packageReceived(QByteArray,int,QString,int,int)), |
62 | receiver: this, SLOT(readPackage(QByteArray,int,QString,int,int))); |
63 | |
64 | if (endType == CLIENT) { |
65 | QDBusServiceWatcher *watcher = new QDBusServiceWatcher(interface->service(), |
66 | interface->connection(), |
67 | QDBusServiceWatcher::WatchForUnregistration); |
68 | |
69 | QObject::connect(sender: watcher, SIGNAL(serviceUnregistered(QString)), |
70 | receiver: this, SLOT(serviceRemoved(QString))); |
71 | } |
72 | } |
73 | |
74 | ~DBusEndPoint() |
75 | { |
76 | } |
77 | |
78 | public Q_SLOTS: |
79 | void closeIncoming() |
80 | { |
81 | QDBusMessage msg = interface->callWithArgumentList(mode: QDBus::AutoDetect, method: QLatin1String("closeIncoming" ), |
82 | args: QList<QVariant>() << instanceId); |
83 | } |
84 | |
85 | void setInstanceId(const QString& id) |
86 | { |
87 | instanceId = id; |
88 | } |
89 | |
90 | Q_SIGNALS: |
91 | void ipcFault(QService::UnrecoverableIPCError); |
92 | |
93 | protected: |
94 | void flushPackage(const QServicePackage& package) |
95 | { |
96 | if (!QDBusConnection::sessionBus().isConnected()) { |
97 | qWarning() << "Cannot connect to DBus" ; |
98 | } |
99 | |
100 | QByteArray block; |
101 | QDataStream out(&block, QIODevice::WriteOnly); |
102 | out.setVersion(QDataStream::Qt_4_6); |
103 | out << package; |
104 | |
105 | packageId = package.d->messageId.toString(); |
106 | interface->asyncCall(method: QLatin1String("writePackage" ), args&: block, args&: endType, args&: packageId); |
107 | } |
108 | |
109 | protected Q_SLOTS: |
110 | void readPackage(const QByteArray &package, int type, const QString &id, int pid, int uid) { |
111 | // Check that its of a client-server nature |
112 | if (endType != type) { |
113 | // Client to Server |
114 | if (type != SERVER) { |
115 | readIncoming(package, pid, uid); |
116 | } else { |
117 | // Server to Client |
118 | if (id == packageId) { |
119 | readIncoming(package, pid, uid); |
120 | } |
121 | } |
122 | } |
123 | } |
124 | |
125 | void readIncoming(const QByteArray &package, int pid, int uid) |
126 | { |
127 | QDataStream data(package); |
128 | QServicePackage pack; |
129 | data >> pack; |
130 | |
131 | dbus_creds dcreds; |
132 | |
133 | dcreds.pid = pid; |
134 | dcreds.uid = uid; |
135 | |
136 | creds_list.enqueue(t: dcreds); |
137 | incoming.enqueue(t: pack); |
138 | emit readyRead(); |
139 | } |
140 | |
141 | void serviceRemoved(const QString& name) |
142 | { |
143 | Q_UNUSED(name); |
144 | QString serviceName = interface->service(); |
145 | QDBusReply<bool> reply = interface->connection().interface()->isServiceRegistered(serviceName); |
146 | if (!reply.value()) { |
147 | emit ipcFault(QService::ErrorServiceNoLongerAvailable); |
148 | } |
149 | } |
150 | |
151 | void getSecurityCredentials(QServiceClientCredentials &creds) |
152 | { |
153 | if (!creds_list.isEmpty()) { |
154 | dbus_creds dcreds = creds_list.dequeue(); |
155 | creds.d->pid = dcreds.pid; |
156 | creds.d->uid = dcreds.uid; |
157 | creds.d->gid = -1; |
158 | } |
159 | } |
160 | |
161 | private: |
162 | QDBusInterface* interface; |
163 | QString packageId; |
164 | int endType; |
165 | QString instanceId; |
166 | QQueue<dbus_creds> creds_list; |
167 | }; |
168 | |
169 | class DBusSessionAdaptor: public QDBusAbstractAdaptor |
170 | { |
171 | Q_OBJECT |
172 | Q_CLASSINFO("D-Bus Interface" , "com.nokia.qtmobility.sfw.DBusSession" ) |
173 | |
174 | public: |
175 | DBusSessionAdaptor(QObject *parent); |
176 | ~DBusSessionAdaptor() {} |
177 | |
178 | public Q_SLOTS: |
179 | QByteArray writePackage(const QByteArray &package, int type, const QString &id) { |
180 | QByteArray ret; |
181 | QMetaObject::invokeMethod(obj: parent(), member: "writePackage" , |
182 | Q_RETURN_ARG(QByteArray, ret), |
183 | Q_ARG(QByteArray, package), |
184 | Q_ARG(int, type), |
185 | Q_ARG(QString, id)); |
186 | return ret; |
187 | } |
188 | |
189 | bool processIncoming() { |
190 | bool ret; |
191 | QMetaObject::invokeMethod(obj: parent(), member: "processIncoming" , |
192 | Q_RETURN_ARG(bool, ret)); |
193 | return ret; |
194 | } |
195 | |
196 | void acceptIncoming(bool accept) { |
197 | QMetaObject::invokeMethod(obj: parent(), member: "acceptIncoming" , |
198 | Q_ARG(bool, accept)); |
199 | } |
200 | |
201 | void closeIncoming(const QString& instanceId) { |
202 | QMetaObject::invokeMethod(obj: parent(), member: "closeIncoming" , |
203 | Q_ARG(QString, instanceId)); |
204 | } |
205 | |
206 | void q_autostart() { |
207 | } |
208 | |
209 | Q_SIGNALS: |
210 | void packageReceived(const QByteArray &package, int type, const QString &id, int uid, int pid); |
211 | void newConnection(int pid, int uid); |
212 | }; |
213 | |
214 | DBusSessionAdaptor::DBusSessionAdaptor(QObject *parent) |
215 | : QDBusAbstractAdaptor(parent) |
216 | { |
217 | setAutoRelaySignals(true); |
218 | } |
219 | |
220 | QRemoteServiceRegisterDBusPrivate::QRemoteServiceRegisterDBusPrivate(QObject* parent) |
221 | : QRemoteServiceRegisterPrivate(parent) |
222 | { |
223 | } |
224 | |
225 | QRemoteServiceRegisterDBusPrivate::~QRemoteServiceRegisterDBusPrivate() |
226 | { |
227 | } |
228 | |
229 | void QRemoteServiceRegisterDBusPrivate::publishServices(const QString& ident) |
230 | { |
231 | if (!createServiceEndPoint(ident)) |
232 | QTimer::singleShot(msec: 0, receiver: QCoreApplication::instance(), SLOT(quit())); |
233 | } |
234 | |
235 | /*! |
236 | Creates endpoint on service side. |
237 | */ |
238 | bool QRemoteServiceRegisterDBusPrivate::createServiceEndPoint(const QString& ident) |
239 | { |
240 | int endPoints = 0; |
241 | |
242 | InstanceManager *iManager = InstanceManager::instance(); |
243 | QList<QRemoteServiceRegister::Entry> list = iManager->allEntries(); |
244 | |
245 | if (list.size() < 1) |
246 | return false; |
247 | |
248 | QDBusConnection connection = QDBusConnection::sessionBus(); |
249 | if (!connection.isConnected()) { |
250 | qWarning() << "Cannot connect to DBus" ; |
251 | return 0; |
252 | } |
253 | |
254 | // Registers the service and session object on DBus if needed |
255 | for (int i=0; i<list.size(); i++) { |
256 | QString serviceName = QStringLiteral("com.nokia.qtmobility.sfw." ) + list.at(i).serviceName(); |
257 | QDBusReply<bool> reply = connection.interface()->isServiceRegistered(serviceName); |
258 | if (reply.value()) |
259 | continue; |
260 | |
261 | if (!connection.registerService(serviceName)) { |
262 | qWarning() << "Cannot register service to DBus:" << serviceName; |
263 | continue; |
264 | } |
265 | |
266 | // Create and register our DBusSession server/client |
267 | session = new DBusSession(this); |
268 | new DBusSessionAdaptor(session); |
269 | QObject::connect(sender: session, SIGNAL(newConnection(int,int)), |
270 | receiver: this, SLOT(processIncoming(int,int))); |
271 | |
272 | QString path = QLatin1Char('/') + list.at(i).interfaceName() + QLatin1Char('/') + ident; |
273 | path.replace(before: QLatin1Char('.'), after: QLatin1Char('/')); |
274 | if (!connection.objectRegisteredAt(path)) { |
275 | if (!connection.registerObject(path, object: session)) { |
276 | qWarning() << "Cannot register service session to DBus:" << path; |
277 | continue; |
278 | } |
279 | |
280 | iface = new QDBusInterface(serviceName, path, QString(), QDBusConnection::sessionBus()); |
281 | if (!iface->isValid()) { |
282 | qWarning() << "createServiceEndPoint: Cannot connect to remote service" << serviceName << path;; |
283 | qWarning() << QString::fromLatin1(str: "%1 %2 %3" ).arg(a: iface->lastError().name()). |
284 | arg(a: iface->lastError().message()). |
285 | arg(a: iface->lastError().type()); |
286 | continue; |
287 | } |
288 | |
289 | DBusEndPoint* ipcEndPoint = new DBusEndPoint(iface, SERVER); |
290 | ObjectEndPoint* endPoint = new ObjectEndPoint(ObjectEndPoint::Service, ipcEndPoint, this); |
291 | |
292 | // Connect session process disconnections |
293 | QObject::connect(sender: session, SIGNAL(closeConnection(QString,QString)), |
294 | receiver: endPoint, SLOT(disconnected(QString,QString))); |
295 | |
296 | endPoints++; |
297 | } |
298 | } |
299 | |
300 | if (endPoints > 0) |
301 | return true; |
302 | |
303 | return false; |
304 | } |
305 | |
306 | void QRemoteServiceRegisterDBusPrivate::processIncoming(int pid, int uid) |
307 | { |
308 | if (getSecurityFilter()) { |
309 | QServiceClientCredentials cred; |
310 | cred.d->pid = pid; |
311 | cred.d->uid = uid; |
312 | cred.d->gid = -1; |
313 | |
314 | getSecurityFilter()(&cred); |
315 | |
316 | if (!cred.isClientAccepted()) { |
317 | session->acceptIncoming(accept: false); |
318 | |
319 | // Close service if no instances |
320 | if (quitOnLastInstanceClosed() && |
321 | InstanceManager::instance()->totalInstances() < 1) |
322 | QCoreApplication::exit(); |
323 | |
324 | return; |
325 | } |
326 | } |
327 | |
328 | session->acceptIncoming(accept: true); |
329 | } |
330 | |
331 | QRemoteServiceRegisterPrivate* QRemoteServiceRegisterPrivate::constructPrivateObject(QObject *parent) |
332 | { |
333 | return new QRemoteServiceRegisterDBusPrivate(parent); |
334 | } |
335 | |
336 | /*! |
337 | Creates endpoint on client side. |
338 | */ |
339 | QObject* QRemoteServiceRegisterPrivate::proxyForService(const QRemoteServiceRegister::Entry& entry, const QString& location) |
340 | { |
341 | const QString serviceName = QStringLiteral("com.nokia.qtmobility.sfw." ) + entry.serviceName(); |
342 | QString path = QLatin1Char('/') + entry.interfaceName() + QLatin1Char('/') + location; |
343 | path.replace(before: QLatin1Char('.'), after: QLatin1Char('/')); |
344 | |
345 | QDBusConnection connection = QDBusConnection::sessionBus(); |
346 | if (!connection.isConnected()) { |
347 | qWarning() << "Cannot connect to DBus" ; |
348 | return 0; |
349 | } |
350 | |
351 | // Dummy call to autostart the service if not running |
352 | connection.call(message: QDBusMessage::createMethodCall(destination: serviceName, path, interface: QString(), QStringLiteral("q_autostart" ))); |
353 | |
354 | QDBusInterface *iface = new QDBusInterface(serviceName, path, QString(), QDBusConnection::sessionBus()); |
355 | if (!iface->isValid()) { |
356 | qWarning() << "ProxyForService: Cannot connect to remote service" << serviceName << path; |
357 | qWarning() << QString::fromLatin1(str: "%1 %2 %3" ).arg(a: iface->lastError().name()). |
358 | arg(a: iface->lastError().message()). |
359 | arg(a: iface->lastError().type()); |
360 | return 0; |
361 | } |
362 | |
363 | QDBusReply<bool> reply = iface->call(mode: QDBus::Block, method: QLatin1String("processIncoming" )); |
364 | if (reply.value()) { |
365 | DBusEndPoint* ipcEndPoint = new DBusEndPoint(iface, CLIENT); |
366 | ObjectEndPoint* endPoint = new ObjectEndPoint(ObjectEndPoint::Client, ipcEndPoint); |
367 | |
368 | QObject *proxy = endPoint->constructProxy(entry); |
369 | ipcEndPoint->setInstanceId(endPoint->getInstanceId()); |
370 | |
371 | if (proxy) { |
372 | QObject::connect(sender: proxy, SIGNAL(destroyed()), receiver: endPoint, SLOT(deleteLater())); |
373 | QObject::connect(sender: proxy, SIGNAL(destroyed()), receiver: ipcEndPoint, SLOT(closeIncoming())); |
374 | QObject::connect(sender: ipcEndPoint, SIGNAL(ipcFault(QService::UnrecoverableIPCError)), |
375 | receiver: proxy, SIGNAL(errorUnrecoverableIPCFault(QService::UnrecoverableIPCError))); |
376 | } |
377 | return proxy; |
378 | } |
379 | |
380 | qDebug() << "Insufficient credentials to load a service instance" ; |
381 | return 0; |
382 | } |
383 | |
384 | /*! |
385 | Returns true is the service is running |
386 | */ |
387 | |
388 | bool QRemoteServiceRegisterPrivate::isServiceRunning(const QRemoteServiceRegister::Entry &entry, const QString & /*location*/) |
389 | { |
390 | QDBusConnection connection = QDBusConnection::sessionBus(); |
391 | if (!connection.isConnected()) { |
392 | qWarning() << "Cannot connect to DBus" ; |
393 | return false; |
394 | } |
395 | |
396 | // Not exacly right, just because it's registered doesn't mean it's running |
397 | QString serviceName = QStringLiteral("com.nokia.qtmobility.sfw." ) + entry.serviceName(); |
398 | QDBusReply<bool> reply = connection.interface()->isServiceRegistered(serviceName); |
399 | return reply.value(); |
400 | } |
401 | |
402 | #include "moc_qremoteserviceregister_dbus_p.cpp" |
403 | #include "qremoteserviceregister_dbus_p.moc" |
404 | QT_END_NAMESPACE |
405 | |