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