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