1 /*
2 * Copyright (C) 2005 Charles Schmidt <cschmidt2@emich.edu>
3 * Copyright (C) 2006 William Jon McCann <mccann@jhu.edu>
4 * Copyright (C) 2006 INDT
5 * Andre Moreira Magalhaes <andre.magalhaes@indt.org.br>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA*
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <libdmapsharing/dmap.h>
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31
32 #include <glib.h>
33 #include <glib/gi18n.h>
34 #include <glib-object.h>
35
36 #include <avahi-client/lookup.h>
37 #include <avahi-client/publish.h>
38 #include <avahi-client/client.h>
39 #include <avahi-common/error.h>
40 #include <avahi-glib/glib-malloc.h>
41 #include <avahi-glib/glib-watch.h>
42
43 struct _DMAPMdnsBrowserPrivate
44 {
45 DMAPMdnsBrowserServiceType service_type;
46 AvahiClient *client;
47 AvahiGLibPoll *poll;
48 AvahiServiceBrowser *service_browser;
49 GSList *services;
50 GSList *resolvers;
51 };
52
53 enum
54 {
55 SERVICE_ADDED,
56 SERVICE_REMOVED,
57 LAST_SIGNAL
58 };
59
60 static void dmap_mdns_browser_class_init (DMAPMdnsBrowserClass * klass);
61 static void dmap_mdns_browser_init (DMAPMdnsBrowser * browser);
62 static void dmap_mdns_browser_dispose (GObject * object);
63 static void dmap_mdns_browser_finalize (GObject * object);
64 static void avahi_client_init (DMAPMdnsBrowser * browser);
65 static void resolve_cb (AvahiServiceResolver * service_resolver,
66 AvahiIfIndex interface,
67 AvahiProtocol protocol,
68 AvahiResolverEvent event,
69 const gchar * service_name,
70 const gchar * type,
71 const gchar * domain,
72 const gchar * host_name,
73 const AvahiAddress * address,
74 uint16_t port, AvahiStringList * text,
75 AvahiLookupResultFlags flags,
76 DMAPMdnsBrowser * browser);
77 static gboolean dmap_mdns_browser_resolve (DMAPMdnsBrowser * browser,
78 const gchar * name,
79 const gchar * domain);
80 static void browser_add_service (DMAPMdnsBrowser * browser,
81 const gchar * service_name,
82 const gchar * domain);
83 static void browser_remove_service (DMAPMdnsBrowser * browser,
84 const gchar * service_name);
85 static void browse_cb (AvahiServiceBrowser * service_browser,
86 AvahiIfIndex interface,
87 AvahiProtocol protocol,
88 AvahiBrowserEvent event,
89 const gchar * name,
90 const gchar * type, const gchar * domain,
91 AvahiLookupResultFlags flags,
92 DMAPMdnsBrowser * browser);
93 static void free_service (DMAPMdnsBrowserService * service);
94
95 static guint dmap_mdns_browser_signals[LAST_SIGNAL] = { 0, };
96
97 G_DEFINE_TYPE_WITH_PRIVATE (DMAPMdnsBrowser, dmap_mdns_browser, G_TYPE_OBJECT);
98
99 GQuark
dmap_mdns_browser_error_quark(void)100 dmap_mdns_browser_error_quark (void)
101 {
102 static GQuark quark = 0;
103
104 if (!quark)
105 quark = g_quark_from_static_string
106 ("dmap_mdns_browser_error");
107
108 return quark;
109 }
110
111 static void
dmap_mdns_browser_class_init(DMAPMdnsBrowserClass * klass)112 dmap_mdns_browser_class_init (DMAPMdnsBrowserClass * klass)
113 {
114 GObjectClass *object_class = G_OBJECT_CLASS (klass);
115
116 dmap_mdns_browser_parent_class = g_type_class_peek_parent (klass);
117
118 object_class->dispose = dmap_mdns_browser_dispose;
119 object_class->finalize = dmap_mdns_browser_finalize;
120
121 dmap_mdns_browser_signals[SERVICE_ADDED] =
122 g_signal_new ("service-added",
123 G_TYPE_FROM_CLASS (object_class),
124 G_SIGNAL_RUN_LAST,
125 G_STRUCT_OFFSET (DMAPMdnsBrowserClass,
126 service_added), NULL, NULL,
127 NULL, G_TYPE_NONE,
128 1, G_TYPE_POINTER);
129 dmap_mdns_browser_signals[SERVICE_REMOVED] =
130 g_signal_new ("service-removed",
131 G_TYPE_FROM_CLASS (object_class),
132 G_SIGNAL_RUN_LAST,
133 G_STRUCT_OFFSET (DMAPMdnsBrowserClass,
134 service_removed), NULL, NULL,
135 NULL, G_TYPE_NONE, 1,
136 G_TYPE_STRING);
137 }
138
139 static void
dmap_mdns_browser_init(DMAPMdnsBrowser * browser)140 dmap_mdns_browser_init (DMAPMdnsBrowser * browser)
141 {
142 browser->priv = dmap_mdns_browser_get_instance_private(browser);
143 avahi_client_init (browser);
144 }
145
146 static void
_wrapper(void * p)147 _wrapper(void *p)
148 {
149 avahi_service_resolver_free(p);
150 }
151
152 static void
dmap_mdns_browser_dispose(GObject * object)153 dmap_mdns_browser_dispose (GObject * object)
154 {
155 DMAPMdnsBrowser *browser = DMAP_MDNS_BROWSER (object);
156 GSList *walk;
157 DMAPMdnsBrowserService *service;
158
159 for (walk = browser->priv->services; walk; walk = walk->next) {
160 service = (DMAPMdnsBrowserService *) walk->data;
161 free_service (service);
162 }
163 g_slist_free (browser->priv->services);
164
165 if (browser->priv->resolvers) {
166 g_slist_free_full (browser->priv->resolvers, _wrapper);
167 }
168
169 if (browser->priv->service_browser) {
170 avahi_service_browser_free (browser->priv->service_browser);
171 }
172
173 if (browser->priv->client) {
174 avahi_client_free (browser->priv->client);
175 }
176
177 if (browser->priv->poll) {
178 avahi_glib_poll_free (browser->priv->poll);
179 }
180
181 G_OBJECT_CLASS (dmap_mdns_browser_parent_class)->dispose (object);
182 }
183
184 static void
dmap_mdns_browser_finalize(GObject * object)185 dmap_mdns_browser_finalize (GObject * object)
186 {
187 g_signal_handlers_destroy (object);
188 G_OBJECT_CLASS (dmap_mdns_browser_parent_class)->finalize (object);
189 }
190
191 DMAPMdnsBrowser *
dmap_mdns_browser_new(DMAPMdnsBrowserServiceType type)192 dmap_mdns_browser_new (DMAPMdnsBrowserServiceType type)
193 {
194 DMAPMdnsBrowser *browser_object;
195
196 g_return_val_if_fail (type >= DMAP_MDNS_BROWSER_SERVICE_TYPE_INVALID
197 && type <= DMAP_MDNS_BROWSER_SERVICE_TYPE_LAST,
198 NULL);
199
200 browser_object =
201 DMAP_MDNS_BROWSER (g_object_new
202 (DMAP_TYPE_MDNS_BROWSER, NULL));
203 browser_object->priv->service_type = type;
204
205 return browser_object;
206 }
207
208 gboolean
dmap_mdns_browser_start(DMAPMdnsBrowser * browser,GError ** error)209 dmap_mdns_browser_start (DMAPMdnsBrowser * browser, GError ** error)
210 {
211 if (browser->priv->client == NULL) {
212 g_set_error (error,
213 DMAP_MDNS_BROWSER_ERROR,
214 DMAP_MDNS_BROWSER_ERROR_NOT_RUNNING,
215 "%s", _("MDNS service is not running"));
216 return FALSE;
217 }
218 if (browser->priv->service_browser != NULL) {
219 g_debug ("Browser already active");
220 return TRUE;
221 }
222
223 browser->priv->service_browser =
224 avahi_service_browser_new (browser->priv->client,
225 AVAHI_IF_UNSPEC,
226 AVAHI_PROTO_UNSPEC,
227 service_type_name[browser->
228 priv->service_type],
229 NULL,
230 0,
231 (AvahiServiceBrowserCallback)
232 browse_cb, browser);
233 if (browser->priv->service_browser == NULL) {
234 g_debug ("Error starting mDNS discovery using AvahiServiceBrowser");
235 g_set_error (error,
236 DMAP_MDNS_BROWSER_ERROR,
237 DMAP_MDNS_BROWSER_ERROR_FAILED,
238 "%s", _("Unable to activate browser"));
239
240 return FALSE;
241 }
242
243 return TRUE;
244 }
245
246 gboolean
dmap_mdns_browser_stop(DMAPMdnsBrowser * browser,GError ** error)247 dmap_mdns_browser_stop (DMAPMdnsBrowser * browser, GError ** error)
248 {
249 if (browser->priv->client == NULL) {
250 g_set_error (error,
251 DMAP_MDNS_BROWSER_ERROR,
252 DMAP_MDNS_BROWSER_ERROR_NOT_RUNNING,
253 "%s", _("MDNS service is not running"));
254 return FALSE;
255 }
256 if (browser->priv->service_browser == NULL) {
257 g_set_error (error,
258 DMAP_MDNS_BROWSER_ERROR,
259 DMAP_MDNS_BROWSER_ERROR_FAILED,
260 "%s", _("Browser is not active"));
261 return FALSE;
262 }
263 avahi_service_browser_free (browser->priv->service_browser);
264 browser->priv->service_browser = NULL;
265
266 return TRUE;
267 }
268
269 const GSList *
dmap_mdns_browser_get_services(DMAPMdnsBrowser * browser)270 dmap_mdns_browser_get_services (DMAPMdnsBrowser * browser)
271 {
272 g_return_val_if_fail (browser != NULL, NULL);
273 return browser->priv->services;
274 }
275
276 DMAPMdnsBrowserServiceType
dmap_mdns_browser_get_service_type(DMAPMdnsBrowser * browser)277 dmap_mdns_browser_get_service_type (DMAPMdnsBrowser * browser)
278 {
279 g_return_val_if_fail (browser != NULL,
280 DMAP_MDNS_BROWSER_SERVICE_TYPE_INVALID);
281 return browser->priv->service_type;
282 }
283
284 static void
client_cb(AvahiClient * client,AvahiClientState state,G_GNUC_UNUSED DMAPMdnsBrowser * browser)285 client_cb (AvahiClient * client,
286 AvahiClientState state,
287 G_GNUC_UNUSED DMAPMdnsBrowser * browser)
288 {
289 /* Called whenever the client or server state changes */
290
291 switch (state) {
292 case AVAHI_CLIENT_FAILURE:
293 g_warning ("Client failure: %s\n",
294 avahi_strerror (avahi_client_errno (client)));
295 break;
296 default:
297 break;
298 }
299 }
300
301 static void
avahi_client_init(DMAPMdnsBrowser * browser)302 avahi_client_init (DMAPMdnsBrowser * browser)
303 {
304 gint error = 0;
305 AvahiClientFlags flags = 0;
306
307 avahi_set_allocator (avahi_glib_allocator ());
308
309 browser->priv->poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
310
311 if (!browser->priv->poll) {
312 g_debug ("Unable to create AvahiGlibPoll object for mDNS");
313 }
314
315 browser->priv->client =
316 avahi_client_new (avahi_glib_poll_get
317 (browser->priv->poll), flags,
318 (AvahiClientCallback) client_cb,
319 browser, &error);
320 }
321
322 static void
resolve_cb(AvahiServiceResolver * service_resolver,G_GNUC_UNUSED AvahiIfIndex interface,G_GNUC_UNUSED AvahiProtocol protocol,AvahiResolverEvent event,const gchar * service_name,const gchar * type,const gchar * domain,G_GNUC_UNUSED const gchar * host_name,const AvahiAddress * address,uint16_t port,AvahiStringList * text,G_GNUC_UNUSED AvahiLookupResultFlags flags,DMAPMdnsBrowser * browser)323 resolve_cb (AvahiServiceResolver * service_resolver,
324 G_GNUC_UNUSED AvahiIfIndex interface,
325 G_GNUC_UNUSED AvahiProtocol protocol,
326 AvahiResolverEvent event,
327 const gchar * service_name,
328 const gchar * type,
329 const gchar * domain,
330 G_GNUC_UNUSED const gchar * host_name,
331 const AvahiAddress * address,
332 uint16_t port, AvahiStringList * text,
333 G_GNUC_UNUSED AvahiLookupResultFlags flags,
334 DMAPMdnsBrowser * browser)
335 {
336 gchar *name = NULL;
337 gchar *pair = NULL; /* FIXME: extract DACP-specific items into sub-class. Ensure in Howl and dns-sd code too. */
338 DMAPMdnsBrowserTransportProtocol transport_protocol = DMAP_MDNS_BROWSER_TRANSPORT_PROTOCOL_TCP; // FIXME: subclass
339 gchar host[AVAHI_ADDRESS_STR_MAX];
340 gboolean pp = FALSE;
341 DMAPMdnsBrowserService *service;
342
343 switch (event) {
344 case AVAHI_RESOLVER_FAILURE:
345 g_warning
346 ("Failed to resolve service '%s' of type '%s' in domain '%s': %s\n",
347 service_name, type, domain,
348 avahi_strerror (avahi_client_errno
349 (avahi_service_resolver_get_client
350 (service_resolver))));
351 break;
352 case AVAHI_RESOLVER_FOUND:
353
354 if (text) {
355 AvahiStringList *l;
356
357 for (l = text; l != NULL; l = l->next) {
358 size_t size;
359 gchar *key;
360 gchar *value;
361 gint ret;
362
363 ret = avahi_string_list_get_pair (l, &key,
364 &value,
365 &size);
366 if (ret != 0 || key == NULL) {
367 continue;
368 }
369
370 if (strcmp (key, "Password") == 0) {
371 if (size >= 4
372 && strncmp (value, "true",
373 4) == 0) {
374 pp = TRUE;
375 } else if (size >= 1
376 && strncmp (value, "1",
377 1) == 0) {
378 pp = TRUE;
379 }
380 } else if (strcmp (key, "Machine Name") == 0) {
381 if (name == NULL)
382 name = g_strdup (value);
383 } else if (strcmp (key, "DvNm") == 0) {
384 if (name != NULL)
385 g_free (name);
386 /* Remote's name is presented as DvNm in DACP */
387 name = g_strdup (value);
388 } else if (strcmp (key, "Pair") == 0) {
389 /* Pair is used when first connecting to a DACP remote */
390 pair = g_strdup (value);
391 } else if (strcmp (key, "tp") == 0) {
392 /* RAOP transport protocol */
393 transport_protocol = strstr (value, "UDP")
394 ? DMAP_MDNS_BROWSER_TRANSPORT_PROTOCOL_UDP
395 : DMAP_MDNS_BROWSER_TRANSPORT_PROTOCOL_TCP;
396 }
397
398 g_free (key);
399 g_free (value);
400 }
401 }
402
403 if (name == NULL) {
404 name = g_strdup (service_name);
405 }
406
407 avahi_address_snprint (host, AVAHI_ADDRESS_STR_MAX, address);
408
409 service = g_new0 (DMAPMdnsBrowserService, 1);
410 service->service_name = g_strdup (service_name);
411 service->name = name;
412 service->host = g_strdup (host);
413 service->port = port;
414 service->pair = pair; // FIXME: subclass
415 service->transport_protocol = transport_protocol; // FIXME: subclass
416 service->password_protected = pp;
417 browser->priv->services =
418 g_slist_append (browser->priv->services, service);
419 g_signal_emit (browser,
420 dmap_mdns_browser_signals[SERVICE_ADDED], 0,
421 service);
422 break;
423 default:
424 g_warning ("Unhandled event");
425 break;
426 }
427
428 browser->priv->resolvers =
429 g_slist_remove (browser->priv->resolvers, service_resolver);
430 avahi_service_resolver_free (service_resolver);
431 }
432
433 static gboolean
dmap_mdns_browser_resolve(DMAPMdnsBrowser * browser,const gchar * name,const gchar * domain)434 dmap_mdns_browser_resolve (DMAPMdnsBrowser * browser,
435 const gchar * name, const gchar * domain)
436 {
437 AvahiServiceResolver *service_resolver;
438
439 service_resolver = avahi_service_resolver_new (browser->priv->client,
440 AVAHI_IF_UNSPEC,
441 AVAHI_PROTO_INET,
442 name,
443 service_type_name
444 [browser->
445 priv->service_type],
446 domain,
447 AVAHI_PROTO_UNSPEC,
448 0,
449 (AvahiServiceResolverCallback) resolve_cb, browser);
450 if (service_resolver == NULL) {
451 g_debug ("Error starting mDNS resolving using AvahiServiceResolver");
452 return FALSE;
453 }
454
455 browser->priv->resolvers =
456 g_slist_prepend (browser->priv->resolvers, service_resolver);
457
458 return TRUE;
459 }
460
461 static void
browser_add_service(DMAPMdnsBrowser * browser,const gchar * service_name,const gchar * domain)462 browser_add_service (DMAPMdnsBrowser * browser,
463 const gchar * service_name, const gchar * domain)
464 {
465 dmap_mdns_browser_resolve (browser, service_name, domain);
466 }
467
468 static void
browser_remove_service(DMAPMdnsBrowser * browser,const gchar * service_name)469 browser_remove_service (DMAPMdnsBrowser * browser, const gchar * service_name)
470 {
471 g_signal_emit (browser,
472 dmap_mdns_browser_signals[SERVICE_REMOVED],
473 0, service_name);
474 }
475
476 static void
browse_cb(G_GNUC_UNUSED AvahiServiceBrowser * service_browser,G_GNUC_UNUSED AvahiIfIndex interface,G_GNUC_UNUSED AvahiProtocol protocol,AvahiBrowserEvent event,const gchar * name,G_GNUC_UNUSED const gchar * type,const gchar * domain,AvahiLookupResultFlags flags,DMAPMdnsBrowser * browser)477 browse_cb (G_GNUC_UNUSED AvahiServiceBrowser * service_browser,
478 G_GNUC_UNUSED AvahiIfIndex interface,
479 G_GNUC_UNUSED AvahiProtocol protocol,
480 AvahiBrowserEvent event,
481 const gchar * name,
482 G_GNUC_UNUSED const gchar * type,
483 const gchar * domain,
484 AvahiLookupResultFlags flags,
485 DMAPMdnsBrowser * browser)
486 {
487 gboolean local;
488
489 local = ((flags & AVAHI_LOOKUP_RESULT_LOCAL) != 0);
490
491 if (local && getenv ("LIBDMAPSHARING_ENABLE_LOCAL") == NULL) {
492 g_debug ("Ignoring local service %s", name);
493 return;
494 }
495
496 if (event == AVAHI_BROWSER_NEW) {
497 browser_add_service (browser, name, domain);
498 } else if (event == AVAHI_BROWSER_REMOVE) {
499 browser_remove_service (browser, name);
500 }
501 }
502
503 static void
free_service(DMAPMdnsBrowserService * service)504 free_service (DMAPMdnsBrowserService * service)
505 {
506 g_free (service->service_name);
507 g_free (service->name);
508 g_free (service->host);
509 g_free (service->pair);
510 g_free (service);
511 }
512