File indexing completed on 2024-04-28 15:22:05

0001 /*
0002     This file is part of the KDE project
0003 
0004     SPDX-FileCopyrightText: 2004 Jakub Stachowski <qbast@go2.pl>
0005     SPDX-FileCopyrightText: 2018 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "avahi-servicebrowser_p.h"
0011 #include "avahi_server_interface.h"
0012 #include "avahi_servicebrowser_interface.h"
0013 #include "servicebrowser.h"
0014 #include <QHash>
0015 #include <QHostAddress>
0016 #include <QStringList>
0017 
0018 namespace KDNSSD
0019 {
0020 ServiceBrowser::ServiceBrowser(const QString &type, bool autoResolve, const QString &domain, const QString &subtype)
0021     : d(new ServiceBrowserPrivate(this))
0022 {
0023     Q_D(ServiceBrowser);
0024     d->m_type = type;
0025     d->m_subtype = subtype;
0026     d->m_autoResolve = autoResolve;
0027     d->m_domain = domain;
0028     d->m_timer.setSingleShot(true);
0029 }
0030 
0031 ServiceBrowser::State ServiceBrowser::isAvailable()
0032 {
0033     org::freedesktop::Avahi::Server s(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus());
0034     QDBusReply<int> rep = s.GetState();
0035     return (rep.isValid() && rep.value() == 2) ? Working : Stopped;
0036 }
0037 
0038 ServiceBrowser::~ServiceBrowser() = default;
0039 
0040 bool ServiceBrowser::isAutoResolving() const
0041 {
0042     Q_D(const ServiceBrowser);
0043     return d->m_autoResolve;
0044 }
0045 
0046 void ServiceBrowser::startBrowse()
0047 {
0048     Q_D(ServiceBrowser);
0049     if (d->m_running) {
0050         return;
0051     }
0052 
0053     // Do not race!
0054     // https://github.com/lathiat/avahi/issues/9
0055     // Avahi's DBus API is incredibly racey with signals getting fired
0056     // immediately after a request was made even though we may not yet be
0057     // listening. In lieu of a proper upstream fix for this we'll unfortunately
0058     // have to resort to this hack:
0059     // We register to all signals regardless of path and then filter them once
0060     // we know what "our" path is. This is much more fragile than a proper
0061     // QDBusInterface assisted signal connection but unfortunately the only way
0062     // we can reliably prevent signals getting lost in the race.
0063     // This uses a fancy trick whereby using QDBusMessage as last argument will
0064     // give us the correct signal argument types as well as the underlying
0065     // message so that we may check the message path.
0066     QDBusConnection::systemBus().connect("org.freedesktop.Avahi",
0067                                          "",
0068                                          "org.freedesktop.Avahi.ServiceBrowser",
0069                                          "ItemNew",
0070                                          d,
0071                                          SLOT(gotGlobalItemNew(int, int, QString, QString, QString, uint, QDBusMessage)));
0072     QDBusConnection::systemBus().connect("org.freedesktop.Avahi",
0073                                          "",
0074                                          "org.freedesktop.Avahi.ServiceBrowser",
0075                                          "ItemRemove",
0076                                          d,
0077                                          SLOT(gotGlobalItemRemove(int, int, QString, QString, QString, uint, QDBusMessage)));
0078     QDBusConnection::systemBus()
0079         .connect("org.freedesktop.Avahi", "", "org.freedesktop.Avahi.ServiceBrowser", "AllForNow", d, SLOT(gotGlobalAllForNow(QDBusMessage)));
0080     d->m_dbusObjectPath.clear();
0081 
0082     org::freedesktop::Avahi::Server s(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus());
0083 
0084     QString fullType = d->m_type;
0085     if (!d->m_subtype.isEmpty()) {
0086         fullType = d->m_subtype + QStringLiteral("._sub.") + d->m_type;
0087     }
0088     QDBusReply<QDBusObjectPath> rep = s.ServiceBrowserNew(-1, -1, fullType, domainToDNS(d->m_domain), 0);
0089     if (!rep.isValid()) {
0090         Q_EMIT finished();
0091         return;
0092     }
0093 
0094     d->m_dbusObjectPath = rep.value().path();
0095     d->m_running = true;
0096     d->m_browserFinished = true;
0097 
0098     // This is held because we need to explicitly Free it!
0099     d->m_browser = new org::freedesktop::Avahi::ServiceBrowser(s.service(), d->m_dbusObjectPath, s.connection());
0100 
0101     connect(&d->m_timer, &QTimer::timeout, d, &ServiceBrowserPrivate::browserFinished);
0102     d->m_timer.start(domainIsLocal(d->m_domain) ? TIMEOUT_LAST_SERVICE : TIMEOUT_START_WAN);
0103 }
0104 
0105 void ServiceBrowserPrivate::serviceResolved(bool success)
0106 {
0107     QObject *sender_obj = const_cast<QObject *>(sender());
0108     RemoteService *svr = static_cast<RemoteService *>(sender_obj);
0109     disconnect(svr, SIGNAL(resolved(bool)), this, SLOT(serviceResolved(bool)));
0110     QList<RemoteService::Ptr>::Iterator it = m_duringResolve.begin();
0111     QList<RemoteService::Ptr>::Iterator itEnd = m_duringResolve.end();
0112     while (it != itEnd && svr != (*it).data()) {
0113         ++it;
0114     }
0115     if (it != itEnd) {
0116         if (success) {
0117             m_services += (*it);
0118             Q_EMIT m_parent->serviceAdded(RemoteService::Ptr(svr));
0119         }
0120         m_duringResolve.erase(it);
0121         queryFinished();
0122     }
0123 }
0124 
0125 void ServiceBrowserPrivate::gotGlobalItemNew(int interface,
0126                                              int protocol,
0127                                              const QString &name,
0128                                              const QString &type,
0129                                              const QString &domain,
0130                                              uint flags,
0131                                              QDBusMessage msg)
0132 {
0133     if (!isOurMsg(msg)) {
0134         return;
0135     }
0136     gotNewService(interface, protocol, name, type, domain, flags);
0137 }
0138 
0139 void ServiceBrowserPrivate::gotGlobalItemRemove(int interface,
0140                                                 int protocol,
0141                                                 const QString &name,
0142                                                 const QString &type,
0143                                                 const QString &domain,
0144                                                 uint flags,
0145                                                 QDBusMessage msg)
0146 {
0147     if (!isOurMsg(msg)) {
0148         return;
0149     }
0150     gotRemoveService(interface, protocol, name, type, domain, flags);
0151 }
0152 
0153 void ServiceBrowserPrivate::gotGlobalAllForNow(QDBusMessage msg)
0154 {
0155     if (!isOurMsg(msg)) {
0156         return;
0157     }
0158     browserFinished();
0159 }
0160 
0161 RemoteService::Ptr ServiceBrowserPrivate::find(RemoteService::Ptr s, const QList<RemoteService::Ptr> &where) const
0162 {
0163     for (const RemoteService::Ptr &i : where)
0164         if (*s == *i) {
0165             return i;
0166         }
0167     return RemoteService::Ptr();
0168 }
0169 
0170 void ServiceBrowserPrivate::gotNewService(int, int, const QString &name, const QString &type, const QString &domain, uint)
0171 {
0172     m_timer.start(TIMEOUT_LAST_SERVICE);
0173     RemoteService::Ptr svr(new RemoteService(name, type, domain));
0174     if (m_autoResolve) {
0175         connect(svr.data(), SIGNAL(resolved(bool)), this, SLOT(serviceResolved(bool)));
0176         m_duringResolve += svr;
0177         svr->resolveAsync();
0178     } else {
0179         m_services += svr;
0180         Q_EMIT m_parent->serviceAdded(svr);
0181     }
0182 }
0183 
0184 void ServiceBrowserPrivate::gotRemoveService(int, int, const QString &name, const QString &type, const QString &domain, uint)
0185 {
0186     m_timer.start(TIMEOUT_LAST_SERVICE);
0187     RemoteService::Ptr tmpl(new RemoteService(name, type, domain));
0188     RemoteService::Ptr found = find(tmpl, m_duringResolve);
0189     if (found) {
0190         m_duringResolve.removeAll(found);
0191         return;
0192     }
0193     found = find(tmpl, m_services);
0194     if (!found) {
0195         return;
0196     }
0197 
0198     Q_EMIT m_parent->serviceRemoved(found);
0199     m_services.removeAll(found);
0200 }
0201 void ServiceBrowserPrivate::browserFinished()
0202 {
0203     m_timer.stop();
0204     m_browserFinished = true;
0205     queryFinished();
0206 }
0207 
0208 void ServiceBrowserPrivate::queryFinished()
0209 {
0210     if (!m_duringResolve.count() && m_browserFinished) {
0211         Q_EMIT m_parent->finished();
0212     }
0213 }
0214 
0215 QList<RemoteService::Ptr> ServiceBrowser::services() const
0216 {
0217     Q_D(const ServiceBrowser);
0218     return d->m_services;
0219 }
0220 
0221 void ServiceBrowser::virtual_hook(int, void *)
0222 {
0223 }
0224 
0225 QHostAddress ServiceBrowser::resolveHostName(const QString &hostname)
0226 {
0227     org::freedesktop::Avahi::Server s(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus());
0228 
0229     int protocol = 0;
0230     QString name;
0231     int aprotocol = 0;
0232     QString address;
0233     uint flags = 0;
0234 
0235     QDBusReply<int> reply = s.ResolveHostName(-1, -1, hostname, 0, (unsigned int)0, protocol, name, aprotocol, address, flags);
0236 
0237     if (reply.isValid()) {
0238         return QHostAddress(address);
0239     } else {
0240         return QHostAddress();
0241     }
0242 }
0243 
0244 QString ServiceBrowser::getLocalHostName()
0245 {
0246     org::freedesktop::Avahi::Server s(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus());
0247 
0248     QDBusReply<QString> reply = s.GetHostName();
0249 
0250     if (reply.isValid()) {
0251         return reply.value();
0252     } else {
0253         return QString();
0254     }
0255 }
0256 
0257 }
0258 
0259 #include "moc_avahi-servicebrowser_p.cpp"
0260 #include "moc_servicebrowser.cpp"