swift
dbusserver.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "misc/dbusserver.h"
5 
6 #include <QDBusServer>
7 #include <QMetaClassInfo>
8 #include <QMetaObject>
9 #include <QStringList>
10 #include <QtGlobal>
11 
12 #include "misc/logmessage.h"
14 #include "misc/statusmessage.h"
15 
16 using namespace swift::misc::network;
17 
18 namespace swift::misc
19 {
20  CDBusServer::CDBusServer(const QString &service, const QString &address, QObject *parent) : QObject(parent)
21  {
22  // Application Options:
23  // -h, –host=HOSTNAME Hostname or IP of the remote host
24  // -p, –port-ssh=PORT-SSH SSH port on the remote host
25  // -u, –username=USERNAME SSH username on the remote host
26  // -w, –password=PASSWORD SSH password on the remote host
27  // -m, –method=DBUS_TRANSPORT_METHOD The D-Bus transport method to use (TCP, UNIX, abstract-UNIX)
28  // -b, –bind=HOSTNAME The bind-address to listen for D-Bus client connections on
29  // -d, –bus-address=BUS_ADDRESS The DBus session bus address of the remote D-Bus daemon
30  // -t, –port-tcp=PORT-TCP The TCP port to listen for DBus client connections on
31  // -v, –verbose=VERBOSE Set verbosity level (3, 2, 1, 0, -1)=(packet,protocol,functions,important,none)
32 
33  static const QString desc("Mode: %1 Address: '%2' Service: '%3'");
34  m_serverMode = CDBusServer::modeOfAddress(address);
35  this->setObjectName(
36  desc.arg(CDBusServer::modeToString(m_serverMode), address, service.isEmpty() ? "-" : service));
37  switch (m_serverMode)
38  {
39  case SERVERMODE_SESSIONBUS:
40  {
41  QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, coreServiceName());
42  connection.unregisterService(service); // allow reconnecting by removing still registered service
43  if (!connection.isConnected() || !connection.registerService(service))
44  {
45  // registration fails can either mean something wrong with DBus or service already exists
46  CLogMessage(this).warning(u"DBus registration: %1") << connection.lastError().message();
47  CLogMessage(this).warning(u"Cannot register DBus service, check server running: dbus-daemon.exe "
48  u"--session --address=tcp:host=192.168.0.133,port=45000");
49  }
50  }
51  break;
52  case SERVERMODE_SYSTEMBUS:
53  {
54  QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SystemBus, coreServiceName());
55  connection.unregisterService(service); // allow reconnecting by removing still registered service
56  if (!connection.isConnected() || !connection.registerService(service))
57  {
58  // registration fails can either mean something wrong with DBus or service already exists
59  CLogMessage(this).warning(u"DBus registration: %1") << connection.lastError().message();
60  CLogMessage(this).warning(u"Cannot register DBus service, check server running: dbus-daemon.exe "
61  u"--system --address=tcp:host=192.168.0.133,port=45000");
62  }
63  }
64  break;
65  case SERVERMODE_P2P:
66  default:
67  {
68  QString dbusAddress = isQtDBusAddress(address) ? address : "tcp:host=127.0.0.1,port=45000";
69  dbusAddress = dbusAddress.toLower().trimmed().replace(' ', "");
70  if (!dbusAddress.contains("bind="))
71  {
72  dbusAddress = dbusAddress.append(",bind=*");
73  } // bind to all network interfaces
74 
75  m_busServer.reset(new QDBusServer(dbusAddress, this));
76  m_busServer->setObjectName("QDBusServer: " + this->objectName());
77  m_busServer->setAnonymousAuthenticationAllowed(true);
78 
79  // Note: P2P has no service name
80  if (m_busServer->isConnected())
81  {
82  CLogMessage(this).info(u"DBus P2P Server listening on address: '%1'") << m_busServer->address();
83  }
84  else { CLogMessage(this).warning(u"DBus P2P connection failed: %1") << lastQDBusServerError().message(); }
85  connect(m_busServer.data(), &QDBusServer::newConnection, this,
86  &CDBusServer::registerObjectsWithP2PConnection);
87  }
88  break;
89  }
90  }
91 
93  {
94  this->removeAllObjects();
95  QDBusConnection::disconnectFromBus(coreServiceName());
96  }
97 
99  {
100  static const QString sn = SWIFT_SERVICENAME;
101  return sn;
102  }
103 
104  const QString &CDBusServer::coreServiceName(const QDBusConnection &connection)
105  {
106  static const QString empty;
107  return CDBusServer::isP2PConnection(connection) ? empty : CDBusServer::coreServiceName();
108  }
109 
110  const QStringList &CDBusServer::getLogCategories()
111  {
112  static const QStringList cats({ CLogCategories::dbus() });
113  return cats;
114  }
115 
116  bool CDBusServer::isP2PAddress(const QString &address) { return modeOfAddress(address) == SERVERMODE_P2P; }
117 
118  bool CDBusServer::isP2PConnection(const QDBusConnection &connection)
119  {
120  if (CDBusServer::isQtDefaultConnection(connection)) { return false; }
121  return connection.name().contains(p2pConnectionName());
122  }
123 
124  bool CDBusServer::dBusAddressToHostAndPort(const QString &address, QString &host, int &port)
125  {
126  const QString canonicalAddress = address.trimmed().toLower().replace(' ', "");
127  if (canonicalAddress.contains("host=") || canonicalAddress.contains("port="))
128  {
129  // "tcp:host=foo.com,port=123"
130  const QStringList parts(canonicalAddress.split(','));
131  for (const QString &part : parts)
132  {
133  // "host=" or "tcp:host="
134  if (part.contains("host=", Qt::CaseInsensitive))
135  {
136  const QString h = part.mid(part.lastIndexOf("=") + 1).trimmed();
137  host = h;
138  }
139  else if (part.contains("port=", Qt::CaseInsensitive))
140  {
141  const QString p = part.mid(part.lastIndexOf("=") + 1).trimmed();
142  bool ok;
143  port = p.toInt(&ok);
144  if (!ok) { port = -1; }
145  }
146  }
147  if (port < 0) { port = 45000; }
148  if (host.isEmpty()) { host = "127.0.0.1"; }
149  return true;
150  }
151  else
152  {
153  host = "";
154  port = -1;
155  return false;
156  }
157  }
158 
159  bool CDBusServer::dBusAddressToHostAndPort(const QString &dbusAddress, QString &o_host, QString &o_port)
160  {
161  int port;
162  const bool s = dBusAddressToHostAndPort(dbusAddress, o_host, port);
163  o_port = QString::number(port);
164  return s;
165  }
166 
167  bool CDBusServer::isQtDefaultConnection(const QDBusConnection &connection)
168  {
169  return connection.name() == QDBusConnection::sessionBus().name() ||
170  connection.name() == QDBusConnection::systemBus().name();
171  }
172 
173  bool CDBusServer::isQtDBusAddress(const QString &address)
174  {
175  return address.startsWith("tcp:") || address.startsWith("unix:");
176  }
177 
178  bool CDBusServer::isSessionOrSystemAddress(const QString &address)
179  {
180  return address == sessionBusAddress() || address == systemBusAddress();
181  }
182 
183  QString CDBusServer::getDBusInterfaceFromClassInfo(QObject *object)
184  {
185  if (!object) { return {}; }
186  const QMetaObject *mo = object->metaObject();
187  for (int i = 0; i < mo->classInfoCount(); i++)
188  {
189  const QMetaClassInfo ci = mo->classInfo(i);
190  const QString name = QString(ci.name()).toLower();
191  if (name == "d-bus interface") { return QString(ci.value()); }
192  }
193  return {};
194  }
195 
196  QDBusConnection::RegisterOptions CDBusServer::registerOptions()
197  {
198  return QDBusConnection::ExportAdaptors | QDBusConnection::ExportAllSignals | QDBusConnection::ExportAllSlots;
199  }
200 
201  bool CDBusServer::registerObjectsWithP2PConnection(QDBusConnection connection)
202  {
203  Q_ASSERT(!m_objects.isEmpty());
204  m_connections.insert(connection.name(), connection);
205  CLogMessage(this).info(u"New Connection from: '%1'") << connection.name();
206  bool success = true;
207  for (auto i = m_objects.cbegin(); i != m_objects.cend(); ++i)
208  {
209  const QString key(i.key());
210  const bool ok = connection.registerObject(key, i.value(), registerOptions());
211  if (ok)
212  {
213  CLogMessage(this).info(u"Adding '%1' to the new connection '%2'")
214  << key << this->getDBusInterfaceFromClassInfo(i.value());
215  }
216  else
217  {
218  CLogMessage(this).info(u"Adding '%1' failed, connection '%2', error '%3'")
219  << key << this->getDBusInterfaceFromClassInfo(i.value()) << connection.lastError().message();
220  success = false;
221  }
222  }
223  return success;
224  }
225 
226  void CDBusServer::addObject(const QString &path, QObject *object)
227  {
228  if (!object) { return; }
229  m_objects.insert(path, object); // will be registered when P2P connection is established
230 
231  QObject::connect(object, &QObject::destroyed, this, [this, path] { m_objects.remove(path); });
232 
233  switch (m_serverMode)
234  {
235  case SERVERMODE_SESSIONBUS:
236  {
237  QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, coreServiceName());
238  if (connection.registerObject(path, object, registerOptions()))
239  {
240  CLogMessage(this).info(u"Adding '%1' '%2' to session DBus")
241  << path << getDBusInterfaceFromClassInfo(object);
242  }
243  else
244  {
245  CLogMessage(this).error(u"Error adding '%1' '%2' to session DBus: '%3'")
246  << path << getDBusInterfaceFromClassInfo(object) << connection.lastError().message();
247  }
248  }
249  break;
250  case SERVERMODE_SYSTEMBUS:
251  {
252  QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SystemBus, coreServiceName());
253  if (connection.registerObject(path, object, registerOptions()))
254  {
255  CLogMessage(this).info(u"Adding '%1' '%2' to system DBus")
256  << path << getDBusInterfaceFromClassInfo(object);
257  }
258  else
259  {
260  CLogMessage(this).error(u"Error adding '%1' '%2' to system DBus: '%3'")
261  << path << getDBusInterfaceFromClassInfo(object) << connection.lastError().message();
262  }
263  }
264  break;
265  case SERVERMODE_P2P:
266  {
267  for (QDBusConnection connection : std::as_const(m_connections))
268  {
269  if (connection.registerObject(path, object, registerOptions()))
270  {
271  CLogMessage(this).info(u"Adding '%1' '%2' to P2P DBus '%3'")
272  << path << getDBusInterfaceFromClassInfo(object) << connection.name();
273  }
274  else
275  {
276  CLogMessage(this).error(u"Error adding '%1' '%2' to P2P DBus '%3': '%4'")
277  << path << getDBusInterfaceFromClassInfo(object) << connection.name()
278  << connection.lastError().message();
279  }
280  }
281  }
282  break;
283  default: Q_ASSERT_X(false, Q_FUNC_INFO, "Wrong server mode");
284  }
285  }
286 
288  {
289  if (!hasQDBusServer()) { return {}; }
290  return m_busServer->lastError();
291  }
292 
293  const QDBusServer *CDBusServer::qDBusServer() const { return m_busServer.data(); }
294 
295  bool CDBusServer::hasQDBusServer() const { return !m_busServer.isNull(); }
296 
298  {
299  for (const QString &path : makeKeysRange(std::as_const(m_objects)))
300  {
301  switch (m_serverMode)
302  {
303  case SERVERMODE_SESSIONBUS: QDBusConnection::sessionBus().unregisterObject(path); break;
304  case SERVERMODE_SYSTEMBUS: QDBusConnection::systemBus().unregisterObject(path); break;
305  case SERVERMODE_P2P:
306  for (QDBusConnection connection : std::as_const(m_connections)) { connection.unregisterObject(path); }
307  break;
308  }
309  }
310  m_objects.clear();
311  }
312 
313  const QDBusConnection &CDBusServer::defaultConnection()
314  {
315  static const QDBusConnection defaultConnection("default");
316  return defaultConnection;
317  }
318 
320  {
321  static const QString session("session");
322  return session;
323  }
324 
326  {
327  static const QString system("system");
328  return system;
329  }
330 
331  QDBusConnection CDBusServer::connectToDBus(const QString &dBusAddress, const QString &name)
332  {
333  if (dBusAddress == sessionBusAddress())
334  {
335  if (name.isEmpty()) return QDBusConnection::sessionBus();
336  return QDBusConnection::connectToBus(QDBusConnection::SessionBus, name);
337  }
338  else if (dBusAddress == systemBusAddress())
339  {
340  if (name.isEmpty()) return QDBusConnection::systemBus();
341  return QDBusConnection::connectToBus(QDBusConnection::SystemBus, name);
342  }
343  else if (isP2PAddress(dBusAddress))
344  {
345  return QDBusConnection::connectToPeer(dBusAddress,
346  name.isEmpty() ? CDBusServer::p2pConnectionName() : name);
347  }
348  return QDBusConnection("invalid");
349  }
350 
351  void CDBusServer::disconnectFromDBus(const QDBusConnection &connection, const QString &dBusAddress)
352  {
353  if (CDBusServer::isQtDefaultConnection(connection)) return; // do not touch the default connections
354  if (CDBusServer::isP2PAddress(dBusAddress)) { QDBusConnection::disconnectFromPeer(connection.name()); }
355  else { QDBusConnection::disconnectFromBus(connection.name()); }
356  }
357 
358  QString CDBusServer::p2pAddress(const QString &host, const QString &port)
359  {
360  QString h = host.trimmed().toLower().remove(' ');
361  if (h.isEmpty()) { h = "127.0.0.1"; }
362 
363  // check port
364  bool ok = false;
365  QString p = port.toLower().trimmed();
366  if (!p.isEmpty())
367  {
368  p.toShort(&ok); // cppcheck-suppress ignoredReturnValue
369  if (!ok)
370  {
371  p = ""; // was not a number
372  }
373  }
374 
375  // can handle host and port separately or combined, e.g. "myhost:1234"
376  if (port.isEmpty())
377  {
378  if (h.startsWith("tcp:") && h.contains("host=") && h.contains("port="))
379  {
380  // looks we already got a full string
381  return h;
382  }
383 
384  // 192.168.5.3:9300 style
385  if (h.contains(":"))
386  {
387  const QStringList parts = h.split(":");
388  h = parts.at(0).trimmed();
389  p = parts.at(1).trimmed();
390  }
391  else { p = "45000"; }
392  }
393 
394  // todo: Replace assert with input validation
395  Q_ASSERT_X(CNetworkUtils::isValidIPv4Address(p), "CDBusServer::p2pAddress", "Wrong IP in String");
396  return QStringLiteral("tcp:host=%1,port=%2").arg(h, p);
397  }
398 
400  {
401  static const QString n("p2pConnection");
402  return n;
403  }
404 
405  QString CDBusServer::normalizeAddress(const QString &address)
406  {
407  const QString lc(address.toLower().trimmed());
408 
409  if (lc.isEmpty()) { return sessionBusAddress(); }
410  if (lc == sessionBusAddress() || lc == systemBusAddress()) { return lc; }
411 
412  // some aliases
413  if (lc.startsWith("sys")) { return systemBusAddress(); }
414  if (lc.startsWith("ses")) { return sessionBusAddress(); }
415 
416  // Qt / P2P
417  if (isQtDBusAddress(address)) { return address; }
418  return p2pAddress(address);
419  }
420 
422  {
423  address = address.toLower();
424  if (address == systemBusAddress()) { return SERVERMODE_SYSTEMBUS; }
425  else if (address == sessionBusAddress()) { return SERVERMODE_SESSIONBUS; }
426  else { return SERVERMODE_P2P; }
427  }
428 
430  {
431  static const QString p2p = "P2P";
432  static const QString session = "session";
433  static const QString system = "system";
434 
435  switch (mode)
436  {
437  case SERVERMODE_P2P: return p2p;
438  case SERVERMODE_SYSTEMBUS: return system;
439  case SERVERMODE_SESSIONBUS:
440  default: break;
441  }
442  return session;
443  }
444 
445  bool CDBusServer::isDBusAvailable(const QString &address, int port, int timeoutMs)
446  {
447  QString unused;
448  return CNetworkUtils::canConnect(address, port, unused, timeoutMs);
449  }
450 
451  bool CDBusServer::isDBusAvailable(const QString &address, int port, QString &message, int timeoutMs)
452  {
453  return CNetworkUtils::canConnect(address, port, message, timeoutMs);
454  }
455 
456  bool CDBusServer::isDBusAvailable(const QString &dBusAddress, QString &message, int timeoutMs)
457  {
458  if (dBusAddress.isEmpty())
459  {
460  message = "No address.";
461  return false;
462  }
463  if (isP2PAddress(dBusAddress))
464  {
465  QString host;
466  int port = -1;
467  return CDBusServer::dBusAddressToHostAndPort(dBusAddress, host, port) ?
468  CDBusServer::isDBusAvailable(host, port, message, timeoutMs) :
469  false;
470  }
471  else
472  {
473  QDBusConnection connection = CDBusServer::connectToDBus(dBusAddress);
474  const bool isConnected = connection.isConnected();
475  message = connection.lastError().message();
476  CDBusServer::disconnectFromDBus(connection, dBusAddress);
477  return isConnected;
478  }
479  }
480 
481  bool CDBusServer::isDBusAvailable(const QString &dbusAddress, int timeoutMs)
482  {
483  QString unused;
484  return CDBusServer::isDBusAvailable(dbusAddress, unused, timeoutMs);
485  }
486 } // namespace swift::misc
static void disconnectFromDBus(const QDBusConnection &connection, const QString &dBusAddress)
Disconnect from Bus/Peer to peer.
Definition: dbusserver.cpp:351
static bool dBusAddressToHostAndPort(const QString &dbusAddress, QString &o_host, int &o_port)
Extract host and port from a DBus address.
Definition: dbusserver.cpp:124
static const QDBusConnection & defaultConnection()
Default connection.
Definition: dbusserver.cpp:313
void removeAllObjects()
Remove all objects added with addObject.
Definition: dbusserver.cpp:297
static const QString & systemBusAddress()
Address denoting a system bus server.
Definition: dbusserver.cpp:325
void addObject(const QString &name, QObject *object)
Add a QObject to be exposed via DBus.
Definition: dbusserver.cpp:226
static QDBusConnection connectToDBus(const QString &dbusAddress, const QString &name={})
Connect to DBus.
Definition: dbusserver.cpp:331
static bool isSessionOrSystemAddress(const QString &address)
True if address is session or system bus address.
Definition: dbusserver.cpp:178
bool hasQDBusServer() const
True if using P2P.
Definition: dbusserver.cpp:295
QDBusError lastQDBusServerError() const
Last error.
Definition: dbusserver.cpp:287
static const QString & modeToString(ServerMode mode)
Mode to string.
Definition: dbusserver.cpp:429
static ServerMode modeOfAddress(QString address)
Return the server mode of the given address.
Definition: dbusserver.cpp:421
static bool isDBusAvailable(const QString &host, int port, int timeoutMs=1500)
Is there a DBus server running at the given address?
Definition: dbusserver.cpp:445
static const QStringList & getLogCategories()
Log categories.
Definition: dbusserver.cpp:110
const QDBusServer * qDBusServer() const
DBus server (if using P2P)
Definition: dbusserver.cpp:293
static QString normalizeAddress(const QString &address)
Turn something like 127.0.0.1:45000 into "tcp:host=127.0.0.1,port=45000".
Definition: dbusserver.cpp:405
ServerMode
Server mode.
Definition: dbusserver.h:49
static const QString & sessionBusAddress()
Address denoting a session bus server.
Definition: dbusserver.cpp:319
static QString p2pAddress(const QString &host, const QString &port="")
Address denoting a P2P server at the given host and port.
Definition: dbusserver.cpp:358
static const QString & p2pConnectionName()
P2P connection name.
Definition: dbusserver.cpp:399
CDBusServer(const QString &address, QObject *parent=nullptr)
Construct a server for the core service.
Definition: dbusserver.h:56
static bool isP2PConnection(const QDBusConnection &connection)
False if address is session or system bus connection.
Definition: dbusserver.cpp:118
static bool isQtDefaultConnection(const QDBusConnection &connection)
Is the given connection one of the default connections?
Definition: dbusserver.cpp:167
static bool isP2PAddress(const QString &address)
False if address is session or system bus address.
Definition: dbusserver.cpp:116
static const QString & coreServiceName()
Default service name.
Definition: dbusserver.cpp:98
virtual ~CDBusServer()
Destructor.
Definition: dbusserver.cpp:92
static bool isQtDBusAddress(const QString &address)
True if a valid Qt DBus address, e.g. "unix:tmpdir=/tmp", "tcp:host=127.0.0.1,port=45000".
Definition: dbusserver.cpp:173
static const QString & dbus()
DBus related.
Definition: logcategories.h:59
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
QString message() const
Private.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
#define SWIFT_SERVICENAME
Service name of DBus service.
Definition: dbusserver.h:23
Free functions in swift::misc.
auto makeKeysRange(const T &container)
Returns a const CRange for iterating over the keys of a Qt associative container.
Definition: range.h:353