1 /*
2     This file is part of the KDE project
3 
4     SPDX-FileCopyrightText: 2004, 2005 Jakub Stachowski <qbast@go2.pl>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "avahi-publicservice_p.h"
10 
11 #include <QCoreApplication>
12 #include <QStringList>
13 
14 #include "publicservice.h"
15 
16 #include <config-kdnssd.h>
17 #if HAVE_SYS_TYPES_H
18 #include <sys/types.h>
19 #endif
20 #include "avahi_entrygroup_interface.h"
21 #include "avahi_server_interface.h"
22 #include "servicebrowser.h"
23 
24 namespace KDNSSD
25 {
PublicService(const QString & name,const QString & type,unsigned int port,const QString & domain,const QStringList & subtypes)26 PublicService::PublicService(const QString &name, const QString &type, unsigned int port, const QString &domain, const QStringList &subtypes)
27     : QObject()
28     , ServiceBase(new PublicServicePrivate(this, name, type, domain, port))
29 {
30     KDNSSD_D;
31     if (domain.isNull()) {
32         d->m_domain = "local.";
33     }
34     d->m_subtypes = subtypes;
35 }
36 
~PublicService()37 PublicService::~PublicService()
38 {
39     stop();
40 }
41 
tryApply()42 void PublicServicePrivate::tryApply()
43 {
44     if (fillEntryGroup()) {
45         commit();
46     } else {
47         m_parent->stop();
48         Q_EMIT m_parent->published(false);
49     }
50 }
51 
gotGlobalStateChanged(int state,const QString & error,QDBusMessage msg)52 void PublicServicePrivate::gotGlobalStateChanged(int state, const QString &error, QDBusMessage msg)
53 {
54     if (!isOurMsg(msg)) {
55         return;
56     }
57     groupStateChanged(state, error);
58 }
59 
setServiceName(const QString & serviceName)60 void PublicService::setServiceName(const QString &serviceName)
61 {
62     KDNSSD_D;
63     d->m_serviceName = serviceName;
64     if (d->m_running) {
65         d->m_group->Reset();
66         d->tryApply();
67     }
68 }
69 
setDomain(const QString & domain)70 void PublicService::setDomain(const QString &domain)
71 {
72     KDNSSD_D;
73     d->m_domain = domain;
74     if (d->m_running) {
75         d->m_group->Reset();
76         d->tryApply();
77     }
78 }
79 
setType(const QString & type)80 void PublicService::setType(const QString &type)
81 {
82     KDNSSD_D;
83     d->m_type = type;
84     if (d->m_running) {
85         d->m_group->Reset();
86         d->tryApply();
87     }
88 }
89 
setSubTypes(const QStringList & subtypes)90 void PublicService::setSubTypes(const QStringList &subtypes)
91 {
92     KDNSSD_D;
93     d->m_subtypes = subtypes;
94     if (d->m_running) {
95         d->m_group->Reset();
96         d->tryApply();
97     }
98 }
99 
subtypes() const100 QStringList PublicService::subtypes() const
101 {
102     KDNSSD_D;
103     return d->m_subtypes;
104 }
105 
setPort(unsigned short port)106 void PublicService::setPort(unsigned short port)
107 {
108     KDNSSD_D;
109     d->m_port = port;
110     if (d->m_running) {
111         d->m_group->Reset();
112         d->tryApply();
113     }
114 }
115 
setTextData(const QMap<QString,QByteArray> & textData)116 void PublicService::setTextData(const QMap<QString, QByteArray> &textData)
117 {
118     KDNSSD_D;
119     d->m_textData = textData;
120     if (d->m_running) {
121         d->m_group->Reset();
122         d->tryApply();
123     }
124 }
125 
isPublished() const126 bool PublicService::isPublished() const
127 {
128     KDNSSD_D;
129     return d->m_published;
130 }
131 
publish()132 bool PublicService::publish()
133 {
134     KDNSSD_D;
135     publishAsync();
136     while (d->m_running && !d->m_published) {
137         QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
138     }
139     return d->m_published;
140 }
141 
stop()142 void PublicService::stop()
143 {
144     KDNSSD_D;
145     if (d->m_group) {
146         d->m_group->Reset();
147     }
148     d->m_running = false;
149     d->m_published = false;
150 }
fillEntryGroup()151 bool PublicServicePrivate::fillEntryGroup()
152 {
153     registerTypes();
154     if (!m_group) {
155         // Do not race!
156         // https://github.com/lathiat/avahi/issues/9
157         // Avahi's DBus API is incredibly racey with signals getting fired
158         // immediately after a request was made even though we may not yet be
159         // listening. In lieu of a proper upstream fix for this we'll unfortunately
160         // have to resort to this hack:
161         // We register to all signals regardless of path and then filter them once
162         // we know what "our" path is. This is much more fragile than a proper
163         // QDBusInterface assisted signal connection but unfortunately the only way
164         // we can reliably prevent signals getting lost in the race.
165         // This uses a fancy trick whereby using QDBusMessage as last argument will
166         // give us the correct signal argument types as well as the underlying
167         // message so that we may check the message path.
168         QDBusConnection::systemBus().connect("org.freedesktop.Avahi",
169                                              "",
170                                              "org.freedesktop.Avahi.EntryGroup",
171                                              "StateChanged",
172                                              this,
173                                              SLOT(gotGlobalStateChanged(int, QString, QDBusMessage)));
174         m_dbusObjectPath.clear();
175 
176         QDBusReply<QDBusObjectPath> rep = m_server->EntryGroupNew();
177         if (!rep.isValid()) {
178             return false;
179         }
180 
181         m_dbusObjectPath = rep.value().path();
182 
183         m_group = new org::freedesktop::Avahi::EntryGroup("org.freedesktop.Avahi", m_dbusObjectPath, QDBusConnection::systemBus());
184     }
185     if (m_serviceName.isNull()) {
186         QDBusReply<QString> rep = m_server->GetHostName();
187         if (!rep.isValid()) {
188             return false;
189         }
190         m_serviceName = rep.value();
191     }
192 
193     QList<QByteArray> txt;
194     QMap<QString, QByteArray>::ConstIterator itEnd = m_textData.constEnd();
195     for (QMap<QString, QByteArray>::ConstIterator it = m_textData.constBegin(); it != itEnd; ++it)
196         if (it.value().isNull()) {
197             txt.append(it.key().toLatin1());
198         } else {
199             txt.append(it.key().toLatin1() + '=' + it.value());
200         }
201 
202     for (;;) {
203         QDBusReply<void> ret = m_group->AddService(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), m_hostName, m_port, txt);
204         if (ret.isValid()) {
205             break;
206         }
207 
208         // serious error, bail out
209         if (ret.error().name() != QLatin1String("org.freedesktop.Avahi.CollisionError")) {
210             return false;
211         }
212 
213         // name collision, try another
214         QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName);
215         if (rep.isValid()) {
216             m_serviceName = rep.value();
217         } else {
218             return false;
219         }
220     }
221 
222     for (const QString &subtype : std::as_const(m_subtypes)) {
223         m_group->AddServiceSubtype(-1, -1, 0, m_serviceName, m_type, domainToDNS(m_domain), subtype);
224     }
225     return true;
226 }
227 
serverStateChanged(int s,const QString &)228 void PublicServicePrivate::serverStateChanged(int s, const QString &)
229 {
230     if (!m_running) {
231         return;
232     }
233     switch (s) {
234     case AVAHI_SERVER_INVALID:
235         m_parent->stop();
236         Q_EMIT m_parent->published(false);
237         break;
238     case AVAHI_SERVER_REGISTERING:
239     case AVAHI_SERVER_COLLISION:
240         if (m_group) {
241             m_group->Reset();
242         }
243         m_collision = true;
244         break;
245     case AVAHI_SERVER_RUNNING:
246         if (m_collision) {
247             m_collision = false;
248             tryApply();
249         }
250     }
251 }
252 
publishAsync()253 void PublicService::publishAsync()
254 {
255     KDNSSD_D;
256     if (d->m_running) {
257         stop();
258     }
259 
260     if (!d->m_server) {
261         d->m_server = new org::freedesktop::Avahi::Server(QStringLiteral("org.freedesktop.Avahi"), QStringLiteral("/"), QDBusConnection::systemBus());
262         connect(d->m_server, SIGNAL(StateChanged(int, QString)), d, SLOT(serverStateChanged(int, QString)));
263     }
264 
265     int state = AVAHI_SERVER_INVALID;
266     QDBusReply<int> rep = d->m_server->GetState();
267 
268     if (rep.isValid()) {
269         state = rep.value();
270     }
271     d->m_running = true;
272     d->m_collision = true; // make it look like server is getting out of collision to force registering
273     d->serverStateChanged(state, QString());
274 }
275 
groupStateChanged(int s,const QString & reason)276 void PublicServicePrivate::groupStateChanged(int s, const QString &reason)
277 {
278     switch (s) {
279     case AVAHI_ENTRY_GROUP_COLLISION: {
280         QDBusReply<QString> rep = m_server->GetAlternativeServiceName(m_serviceName);
281         if (rep.isValid()) {
282             m_parent->setServiceName(rep.value());
283         } else {
284             serverStateChanged(AVAHI_SERVER_INVALID, reason);
285         }
286         break;
287     }
288     case AVAHI_ENTRY_GROUP_ESTABLISHED:
289         m_published = true;
290         Q_EMIT m_parent->published(true);
291         break;
292     case AVAHI_ENTRY_GROUP_FAILURE:
293         serverStateChanged(AVAHI_SERVER_INVALID, reason);
294         break;
295     }
296 }
297 
virtual_hook(int,void *)298 void PublicService::virtual_hook(int, void *)
299 {
300 }
301 
302 }
303 
304 #include "moc_avahi-publicservice_p.cpp"
305 #include "moc_publicservice.cpp"
306