1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2008 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 * Author: David Zeuthen <davidz@redhat.com>
21 */
22
23 /*
24 * TODO: - locking
25 * - cancellation
26 * - error handling
27 */
28
29 #include <config.h>
30 #include <string.h>
31 #include <glib/gi18n-lib.h>
32
33 #include <avahi-client/client.h>
34 #include <avahi-client/lookup.h>
35 #include <avahi-common/error.h>
36 #include <avahi-common/timeval.h>
37 #include <avahi-glib/glib-watch.h>
38 #include <avahi-glib/glib-malloc.h>
39
40 #include <net/if.h>
41
42 #include "gvfsdnssdutils.h"
43 #include "gvfsdnssdresolver.h"
44
45 enum
46 {
47 PROP_0,
48 PROP_ENCODED_TRIPLE,
49 PROP_REQUIRED_TXT_KEYS,
50 PROP_SERVICE_NAME,
51 PROP_SERVICE_TYPE,
52 PROP_DOMAIN,
53 PROP_TIMEOUT_MSEC,
54
55 PROP_IS_RESOLVED,
56 PROP_ADDRESS,
57 PROP_INTERFACE,
58 PROP_PORT,
59 PROP_TXT_RECORDS,
60 };
61
62 enum {
63 CHANGED,
64 LAST_SIGNAL
65 };
66
67 static guint signals[LAST_SIGNAL] = { 0 };
68
69 struct _GVfsDnsSdResolver
70 {
71 GObject parent_instance;
72
73 char *encoded_triple;
74 char *service_name;
75 char *service_type;
76 char *domain;
77 char *required_txt_keys;
78 char **required_txt_keys_broken_out;
79 guint timeout_msec;
80
81 gboolean is_resolved;
82 char *address;
83 gchar *interface;
84 guint port;
85 char **txt_records;
86
87 AvahiServiceResolver *avahi_resolver;
88 guint start_avahi_resolver_id;
89 };
90
91
92 struct _GVfsDnsSdResolverClass
93 {
94 GObjectClass parent_class;
95
96 /* signals */
97 void (*changed) (GVfsDnsSdResolver *resolver);
98 };
99
100 G_DEFINE_TYPE (GVfsDnsSdResolver, g_vfs_dns_sd_resolver, G_TYPE_OBJECT);
101
102 static gboolean resolver_supports_mdns = FALSE;
103 static AvahiClient *global_client = NULL;
104 static gboolean avahi_initialized = FALSE;
105 static void free_global_avahi_client (void);
106 static AvahiClient *get_global_avahi_client (GError **error);
107
108 static void ensure_avahi_resolver (GVfsDnsSdResolver *resolver);
109
110 static void service_resolver_cb (AvahiServiceResolver *resolver,
111 AvahiIfIndex interface,
112 AvahiProtocol protocol,
113 AvahiResolverEvent event,
114 const char *name,
115 const char *type,
116 const char *domain,
117 const char *host_name,
118 const AvahiAddress *a,
119 uint16_t port,
120 AvahiStringList *txt,
121 AvahiLookupResultFlags flags,
122 void *user_data);
123
124
125
126 static GList *resolvers = NULL;
127
128 static void
129 clear_avahi_data (GVfsDnsSdResolver *resolver);
130
131 static void
remove_client_from_resolver(GVfsDnsSdResolver * resolver)132 remove_client_from_resolver (GVfsDnsSdResolver *resolver)
133 {
134 if (resolver->avahi_resolver != NULL)
135 {
136 avahi_service_resolver_free (resolver->avahi_resolver);
137 resolver->avahi_resolver = NULL;
138 }
139
140 clear_avahi_data (resolver);
141 }
142
143 static void
add_client_to_resolver(GVfsDnsSdResolver * resolver)144 add_client_to_resolver (GVfsDnsSdResolver *resolver)
145 {
146 ensure_avahi_resolver (resolver);
147 }
148
149 /* Callback for state changes on the Client */
150 static void
avahi_client_callback(AvahiClient * client,AvahiClientState state,void * userdata)151 avahi_client_callback (AvahiClient *client, AvahiClientState state, void *userdata)
152 {
153 if (global_client == NULL)
154 global_client = client;
155
156 if (state == AVAHI_CLIENT_FAILURE)
157 {
158 if (avahi_client_errno (client) == AVAHI_ERR_DISCONNECTED)
159 {
160 free_global_avahi_client ();
161
162 /* Attempt to reconnect */
163 get_global_avahi_client (NULL);
164 }
165 }
166 else if (state == AVAHI_CLIENT_S_RUNNING)
167 {
168 /* Start resolving again */
169 g_list_foreach (resolvers, (GFunc) add_client_to_resolver, NULL);
170 }
171 }
172
173 static void
free_global_avahi_client(void)174 free_global_avahi_client (void)
175 {
176 /* Remove current resolvers */
177 g_list_foreach (resolvers, (GFunc) remove_client_from_resolver, NULL);
178
179 /* Destroy client */
180 if (global_client)
181 {
182 avahi_client_free (global_client);
183 }
184 global_client = NULL;
185 avahi_initialized = FALSE;
186 }
187
188 static AvahiClient *
get_global_avahi_client(GError ** error)189 get_global_avahi_client (GError **error)
190 {
191 static AvahiGLibPoll *glib_poll = NULL;
192 int avahi_error;
193
194 if (!avahi_initialized)
195 {
196 avahi_initialized = TRUE;
197
198 if (glib_poll == NULL)
199 {
200 avahi_set_allocator (avahi_glib_allocator ());
201 glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
202 }
203
204 /* Create a new AvahiClient instance */
205 global_client = avahi_client_new (avahi_glib_poll_get (glib_poll),
206 AVAHI_CLIENT_NO_FAIL,
207 avahi_client_callback,
208 glib_poll,
209 &avahi_error);
210
211 if (global_client == NULL)
212 {
213 g_set_error (error,
214 G_IO_ERROR,
215 G_IO_ERROR_FAILED,
216 _("Error initializing Avahi: %s"),
217 avahi_strerror (avahi_error));
218 goto out;
219 }
220 }
221
222 out:
223
224 return global_client;
225 }
226
227 static gboolean
start_avahi_resolver(gpointer user_data)228 start_avahi_resolver (gpointer user_data)
229 {
230 GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (user_data);
231 AvahiClient *avahi_client;
232
233 avahi_client = get_global_avahi_client (NULL);
234 if (avahi_client == NULL)
235 goto out;
236
237 resolver->avahi_resolver = avahi_service_resolver_new (avahi_client,
238 AVAHI_IF_UNSPEC,
239 AVAHI_PROTO_UNSPEC,
240 resolver->service_name,
241 resolver->service_type,
242 resolver->domain,
243 AVAHI_PROTO_UNSPEC,
244 0, /* AvahiLookupFlags */
245 service_resolver_cb,
246 resolver);
247
248 out:
249 resolver->start_avahi_resolver_id = 0;
250 g_object_unref (resolver);
251 return FALSE;
252 }
253
254 static void
ensure_avahi_resolver(GVfsDnsSdResolver * resolver)255 ensure_avahi_resolver (GVfsDnsSdResolver *resolver)
256 {
257 if (resolver->avahi_resolver != NULL || resolver->start_avahi_resolver_id != 0)
258 return;
259
260 resolver->start_avahi_resolver_id = g_idle_add (start_avahi_resolver, g_object_ref (resolver));
261 }
262
263 static void
g_vfs_dns_sd_resolver_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)264 g_vfs_dns_sd_resolver_get_property (GObject *object,
265 guint prop_id,
266 GValue *value,
267 GParamSpec *pspec)
268 {
269 GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (object);
270
271 switch (prop_id)
272 {
273 case PROP_ENCODED_TRIPLE:
274 g_value_set_string (value, resolver->encoded_triple);
275 break;
276
277 case PROP_REQUIRED_TXT_KEYS:
278 g_value_set_string (value, resolver->required_txt_keys);
279 break;
280
281 case PROP_SERVICE_NAME:
282 g_value_set_string (value, resolver->service_name);
283 break;
284
285 case PROP_SERVICE_TYPE:
286 g_value_set_string (value, resolver->service_type);
287 break;
288
289 case PROP_DOMAIN:
290 g_value_set_string (value, resolver->domain);
291 break;
292
293 case PROP_TIMEOUT_MSEC:
294 g_value_set_uint (value, resolver->timeout_msec);
295 break;
296
297 case PROP_IS_RESOLVED:
298 g_value_set_boolean (value, resolver->is_resolved);
299 break;
300
301 case PROP_ADDRESS:
302 g_value_set_string (value, resolver->address);
303 break;
304
305 case PROP_INTERFACE:
306 g_value_set_string (value, resolver->interface);
307 break;
308
309 case PROP_PORT:
310 g_value_set_uint (value, resolver->port);
311 break;
312
313 case PROP_TXT_RECORDS:
314 g_value_set_boxed (value, resolver->txt_records);
315 break;
316
317 default:
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
319 }
320 }
321
322 static void
g_vfs_dns_sd_resolver_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)323 g_vfs_dns_sd_resolver_set_property (GObject *object,
324 guint prop_id,
325 const GValue *value,
326 GParamSpec *pspec)
327 {
328 GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (object);
329
330 switch (prop_id)
331 {
332 case PROP_ENCODED_TRIPLE:
333 resolver->encoded_triple = g_strdup (g_value_get_string (value));
334 break;
335
336 case PROP_REQUIRED_TXT_KEYS:
337 resolver->required_txt_keys = g_strdup (g_value_get_string (value));
338 if (resolver->required_txt_keys != NULL)
339 {
340 /* TODO: maybe support escaping ',' */
341 resolver->required_txt_keys_broken_out = g_strsplit (resolver->required_txt_keys, ",", 0);
342 }
343 break;
344
345 case PROP_SERVICE_NAME:
346 resolver->service_name = g_strdup (g_value_get_string (value));
347 break;
348
349 case PROP_SERVICE_TYPE:
350 resolver->service_type = g_strdup (g_value_get_string (value));
351 break;
352
353 case PROP_DOMAIN:
354 resolver->domain = g_strdup (g_value_get_string (value));
355 break;
356
357 case PROP_TIMEOUT_MSEC:
358 resolver->timeout_msec = g_value_get_uint (value);
359 break;
360
361 default:
362 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
363 }
364 }
365
366 static void
g_vfs_dns_sd_resolver_finalize(GObject * object)367 g_vfs_dns_sd_resolver_finalize (GObject *object)
368 {
369 GVfsDnsSdResolver *resolver;
370
371 resolver = G_VFS_DNS_SD_RESOLVER (object);
372
373 g_free (resolver->encoded_triple);
374 g_free (resolver->service_name);
375 g_free (resolver->service_type);
376 g_free (resolver->domain);
377 g_free (resolver->required_txt_keys);
378 g_strfreev (resolver->required_txt_keys_broken_out);
379
380 g_free (resolver->address);
381 g_free (resolver->interface);
382 g_strfreev (resolver->txt_records);
383
384 if (resolver->avahi_resolver != NULL)
385 avahi_service_resolver_free (resolver->avahi_resolver);
386
387 if (resolver->start_avahi_resolver_id != 0)
388 g_source_remove (resolver->start_avahi_resolver_id);
389
390 resolvers = g_list_remove (resolvers, resolver);
391
392 /* free the global avahi client for the last resolver */
393 if (resolvers == NULL)
394 {
395 free_global_avahi_client ();
396 }
397
398 G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->finalize (object);
399 }
400
401 static void
g_vfs_dns_sd_resolver_constructed(GObject * object)402 g_vfs_dns_sd_resolver_constructed (GObject *object)
403 {
404 GVfsDnsSdResolver *resolver;
405
406 resolver = G_VFS_DNS_SD_RESOLVER (object);
407
408 if (resolver->encoded_triple != NULL)
409 {
410 GError *error;
411
412 if (resolver->service_name != NULL)
413 {
414 g_warning ("Ignoring service-name since encoded-triple is already set");
415 g_free (resolver->service_name);
416 resolver->service_name = NULL;
417 }
418
419 if (resolver->service_type != NULL)
420 {
421 g_warning ("Ignoring service-type since encoded-triple is already set");
422 g_free (resolver->service_type);
423 resolver->service_type = NULL;
424 }
425
426 if (resolver->domain != NULL)
427 {
428 g_warning ("Ignoring domain since encoded-triple is already set");
429 g_free (resolver->domain);
430 resolver->domain = NULL;
431 }
432
433
434 error = NULL;
435 if (!g_vfs_decode_dns_sd_triple (resolver->encoded_triple,
436 &(resolver->service_name),
437 &(resolver->service_type),
438 &(resolver->domain),
439 &error))
440 {
441 /* GObject construction can't fail. So whine if the triple isn't valid. */
442 g_warning ("Malformed construction data passed: %s", error->message);
443 g_error_free (error);
444
445 g_free (resolver->encoded_triple);
446 g_free (resolver->service_name);
447 g_free (resolver->service_type);
448 g_free (resolver->domain);
449 resolver->encoded_triple = NULL;
450 resolver->service_name = NULL;
451 resolver->service_type = NULL;
452 resolver->domain = NULL;
453 goto out;
454 }
455 }
456
457 /* Always set encoded triple to what we encode; this is because we can decode
458 * an encoded triple that isn't 100% properly URI encoded, e.g.
459 *
460 * "davidz's public files on quad.fubar.dk._webdav._tcp.local"
461 *
462 * will be properly decoded. But we want to return a properly URI encoded triple
463 *
464 * "davidz%27s%20public%20files%20on%20quad%2efubar%2edk._webdav._tcp.local"
465 *
466 * for e.g. setting the GMountSpec. This is useful because the use can
467 * put the former into the pathbar in a file manager and then it will
468 * be properly rewritten on mount.
469 */
470 g_free (resolver->encoded_triple);
471 resolver->encoded_triple = g_vfs_encode_dns_sd_triple (resolver->service_name,
472 resolver->service_type,
473 resolver->domain);
474
475 /* start resolving immediately */
476 ensure_avahi_resolver (resolver);
477
478 resolvers = g_list_prepend (resolvers, resolver);
479
480 out:
481
482 if (G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->constructed != NULL)
483 G_OBJECT_CLASS (g_vfs_dns_sd_resolver_parent_class)->constructed (object);
484 }
485
486 static void
g_vfs_dns_sd_resolver_class_init(GVfsDnsSdResolverClass * klass)487 g_vfs_dns_sd_resolver_class_init (GVfsDnsSdResolverClass *klass)
488 {
489 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
490
491 resolver_supports_mdns = (avahi_nss_support () > 0);
492
493 gobject_class->get_property = g_vfs_dns_sd_resolver_get_property;
494 gobject_class->set_property = g_vfs_dns_sd_resolver_set_property;
495 gobject_class->finalize = g_vfs_dns_sd_resolver_finalize;
496 gobject_class->constructed = g_vfs_dns_sd_resolver_constructed;
497
498 /**
499 * GVfsDnsSdResolver::changed:
500 * @resolver: The resolver emitting the signal.
501 *
502 * Emitted when resolved data changes.
503 */
504 signals[CHANGED] = g_signal_new ("changed",
505 G_VFS_TYPE_DNS_SD_RESOLVER,
506 G_SIGNAL_RUN_LAST,
507 G_STRUCT_OFFSET (GVfsDnsSdResolverClass, changed),
508 NULL,
509 NULL,
510 g_cclosure_marshal_VOID__VOID,
511 G_TYPE_NONE,
512 0);
513
514
515 /**
516 * GVfsDnsSdResolver:encoded-triple:
517 *
518 * The encoded DNS-SD triple for the resolver.
519 */
520 g_object_class_install_property (gobject_class,
521 PROP_ENCODED_TRIPLE,
522 g_param_spec_string ("encoded-triple",
523 "Encoded triple",
524 "Encoded triple",
525 NULL,
526 G_PARAM_CONSTRUCT_ONLY |
527 G_PARAM_READWRITE |
528 G_PARAM_STATIC_NAME |
529 G_PARAM_STATIC_BLURB |
530 G_PARAM_STATIC_NICK));
531
532 /**
533 * GVfsDnsSdResolver:required-txt-keys:
534 *
535 * A comma separated list of keys that must appear in the TXT
536 * records in order to consider the service being resolved.
537 */
538 g_object_class_install_property (gobject_class,
539 PROP_REQUIRED_TXT_KEYS,
540 g_param_spec_string ("required-txt-keys",
541 "Required TXT keys",
542 "Required TXT keys",
543 NULL,
544 G_PARAM_CONSTRUCT_ONLY |
545 G_PARAM_READWRITE |
546 G_PARAM_STATIC_NAME |
547 G_PARAM_STATIC_BLURB |
548 G_PARAM_STATIC_NICK));
549
550 /**
551 * GVfsDnsSdResolver:service-name:
552 *
553 * The name of the service for the resolver.
554 */
555 g_object_class_install_property (gobject_class,
556 PROP_SERVICE_NAME,
557 g_param_spec_string ("service-name",
558 "Service Name",
559 "Service Name",
560 NULL,
561 G_PARAM_CONSTRUCT_ONLY |
562 G_PARAM_READWRITE |
563 G_PARAM_STATIC_NAME |
564 G_PARAM_STATIC_BLURB |
565 G_PARAM_STATIC_NICK));
566
567 /**
568 * GVfsDnsSdResolver:service-type:
569 *
570 * The type of the service for the resolver.
571 */
572 g_object_class_install_property (gobject_class,
573 PROP_SERVICE_TYPE,
574 g_param_spec_string ("service-type",
575 "Service Type",
576 "Service Type",
577 NULL,
578 G_PARAM_CONSTRUCT_ONLY |
579 G_PARAM_READWRITE |
580 G_PARAM_STATIC_NAME |
581 G_PARAM_STATIC_BLURB |
582 G_PARAM_STATIC_NICK));
583
584 /**
585 * GVfsDnsSdResolver:domain:
586 *
587 * The domain for the resolver.
588 */
589 g_object_class_install_property (gobject_class,
590 PROP_DOMAIN,
591 g_param_spec_string ("domain",
592 "Domain",
593 "Domain",
594 NULL,
595 G_PARAM_CONSTRUCT_ONLY |
596 G_PARAM_READWRITE |
597 G_PARAM_STATIC_NAME |
598 G_PARAM_STATIC_BLURB |
599 G_PARAM_STATIC_NICK));
600
601 /**
602 * GVfsDnsSdResolver:timeout-msec:
603 *
604 * Timeout in milliseconds to use when resolving.
605 */
606 g_object_class_install_property (gobject_class,
607 PROP_TIMEOUT_MSEC,
608 g_param_spec_uint ("timeout-msec",
609 "Timeout in milliseconds",
610 "Timeout in milliseconds",
611 0,
612 G_MAXUINT,
613 5000,
614 G_PARAM_CONSTRUCT |
615 G_PARAM_READWRITE |
616 G_PARAM_STATIC_NAME |
617 G_PARAM_STATIC_BLURB |
618 G_PARAM_STATIC_NICK));
619
620 /**
621 * GVfsDnsSdResolver:is-resolved:
622 *
623 * Whether the service is resolved.
624 */
625 g_object_class_install_property (gobject_class,
626 PROP_IS_RESOLVED,
627 g_param_spec_boolean ("is-resolved",
628 "Is resolved",
629 "Is resolved",
630 FALSE,
631 G_PARAM_READABLE |
632 G_PARAM_STATIC_NAME |
633 G_PARAM_STATIC_BLURB |
634 G_PARAM_STATIC_NICK));
635
636 /**
637 * GVfsDnsSdResolver:address:
638 *
639 * The resolved address.
640 */
641 g_object_class_install_property (gobject_class,
642 PROP_DOMAIN,
643 g_param_spec_string ("address",
644 "Address",
645 "Address",
646 NULL,
647 G_PARAM_READABLE |
648 G_PARAM_STATIC_NAME |
649 G_PARAM_STATIC_BLURB |
650 G_PARAM_STATIC_NICK));
651
652 /**
653 * GVfsDnsSdResolver:interface:
654 *
655 * The resolved interface.
656 */
657 g_object_class_install_property (gobject_class,
658 PROP_INTERFACE,
659 g_param_spec_string ("interface",
660 "Interface",
661 "Interface",
662 NULL,
663 G_PARAM_READABLE |
664 G_PARAM_STATIC_NAME |
665 G_PARAM_STATIC_BLURB |
666 G_PARAM_STATIC_NICK));
667
668 /**
669 * GVfsDnsSdResolver:port:
670 *
671 * The resolved port.
672 */
673 g_object_class_install_property (gobject_class,
674 PROP_PORT,
675 g_param_spec_uint ("port",
676 "Port",
677 "Port",
678 0,
679 65536,
680 0,
681 G_PARAM_READABLE |
682 G_PARAM_STATIC_NAME |
683 G_PARAM_STATIC_BLURB |
684 G_PARAM_STATIC_NICK));
685
686 /**
687 * GVfsDnsSdResolver:txt-records:
688 *
689 * The resolved TXT records.
690 */
691 g_object_class_install_property (gobject_class,
692 PROP_TXT_RECORDS,
693 g_param_spec_boxed ("txt-records",
694 "TXT Records",
695 "TXT Records",
696 G_TYPE_STRV,
697 G_PARAM_READABLE |
698 G_PARAM_STATIC_NAME |
699 G_PARAM_STATIC_BLURB |
700 G_PARAM_STATIC_NICK));
701 }
702
703 static void
g_vfs_dns_sd_resolver_init(GVfsDnsSdResolver * resolver)704 g_vfs_dns_sd_resolver_init (GVfsDnsSdResolver *resolver)
705 {
706 }
707
708 gboolean
g_vfs_dns_sd_resolver_is_resolved(GVfsDnsSdResolver * resolver)709 g_vfs_dns_sd_resolver_is_resolved (GVfsDnsSdResolver *resolver)
710 {
711 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), FALSE);
712 return resolver->is_resolved;
713 }
714
715 const gchar *
g_vfs_dns_sd_resolver_get_encoded_triple(GVfsDnsSdResolver * resolver)716 g_vfs_dns_sd_resolver_get_encoded_triple (GVfsDnsSdResolver *resolver)
717 {
718 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
719 return resolver->encoded_triple;
720 }
721
722 const gchar *
g_vfs_dns_sd_resolver_get_required_txt_keys(GVfsDnsSdResolver * resolver)723 g_vfs_dns_sd_resolver_get_required_txt_keys (GVfsDnsSdResolver *resolver)
724 {
725 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
726 return resolver->required_txt_keys;
727 }
728
729 const gchar *
g_vfs_dns_sd_resolver_get_service_name(GVfsDnsSdResolver * resolver)730 g_vfs_dns_sd_resolver_get_service_name (GVfsDnsSdResolver *resolver)
731 {
732 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
733 return resolver->service_name;
734 }
735
736 const gchar *
g_vfs_dns_sd_resolver_get_service_type(GVfsDnsSdResolver * resolver)737 g_vfs_dns_sd_resolver_get_service_type (GVfsDnsSdResolver *resolver)
738 {
739 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
740 return resolver->service_type;
741 }
742
743 const gchar *
g_vfs_dns_sd_resolver_get_domain(GVfsDnsSdResolver * resolver)744 g_vfs_dns_sd_resolver_get_domain (GVfsDnsSdResolver *resolver)
745 {
746 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
747 return resolver->domain;
748 }
749
750 gchar *
g_vfs_dns_sd_resolver_get_address(GVfsDnsSdResolver * resolver)751 g_vfs_dns_sd_resolver_get_address (GVfsDnsSdResolver *resolver)
752 {
753 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
754 return g_strdup (resolver->address);
755 }
756
757 gchar *
g_vfs_dns_sd_resolver_get_interface(GVfsDnsSdResolver * resolver)758 g_vfs_dns_sd_resolver_get_interface (GVfsDnsSdResolver *resolver)
759 {
760 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
761 return g_strdup (resolver->interface);
762 }
763
764 guint
g_vfs_dns_sd_resolver_get_port(GVfsDnsSdResolver * resolver)765 g_vfs_dns_sd_resolver_get_port (GVfsDnsSdResolver *resolver)
766 {
767 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), (guint) -1);
768 return resolver->port;
769 }
770
771 gchar **
g_vfs_dns_sd_resolver_get_txt_records(GVfsDnsSdResolver * resolver)772 g_vfs_dns_sd_resolver_get_txt_records (GVfsDnsSdResolver *resolver)
773 {
774 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
775 return g_strdupv (resolver->txt_records);
776 }
777
778 gchar *
g_vfs_dns_sd_resolver_lookup_txt_record(GVfsDnsSdResolver * resolver,const gchar * key)779 g_vfs_dns_sd_resolver_lookup_txt_record (GVfsDnsSdResolver *resolver,
780 const gchar *key)
781 {
782 gint n;
783 gchar *result;
784 gsize key_len;
785
786 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), NULL);
787 g_return_val_if_fail (key != NULL, NULL);
788
789 result = NULL;
790
791
792 if (resolver->txt_records == NULL)
793 goto out;
794
795 key_len = strlen (key);
796
797 for (n = 0; resolver->txt_records[n] != NULL; n++)
798 {
799 const gchar *s = resolver->txt_records[n];
800 const gchar *p;
801
802 p = strchr (s, '=');
803 if (p != NULL && (p - s) == key_len)
804 {
805 if (g_ascii_strncasecmp (s,
806 key,
807 p - s) == 0)
808 {
809 result = g_strdup (p + 1);
810 goto out;
811 }
812 }
813 }
814
815 out:
816 return result;
817 }
818
819 GVfsDnsSdResolver *
g_vfs_dns_sd_resolver_new_for_encoded_triple(const gchar * encoded_triple,const gchar * required_txt_keys)820 g_vfs_dns_sd_resolver_new_for_encoded_triple (const gchar *encoded_triple,
821 const gchar *required_txt_keys)
822
823 {
824 g_return_val_if_fail (encoded_triple != NULL, NULL);
825
826 return G_VFS_DNS_SD_RESOLVER (g_object_new (G_VFS_TYPE_DNS_SD_RESOLVER,
827 "encoded-triple", encoded_triple,
828 "required-txt-keys", required_txt_keys,
829 NULL));
830 }
831
832 GVfsDnsSdResolver *
g_vfs_dns_sd_resolver_new_for_service(const gchar * service_name,const gchar * service_type,const gchar * domain,const gchar * required_txt_keys)833 g_vfs_dns_sd_resolver_new_for_service (const gchar *service_name,
834 const gchar *service_type,
835 const gchar *domain,
836 const gchar *required_txt_keys)
837 {
838 g_return_val_if_fail (service_name != NULL, NULL);
839 g_return_val_if_fail (service_type != NULL, NULL);
840 g_return_val_if_fail (domain != NULL, NULL);
841
842 return G_VFS_DNS_SD_RESOLVER (g_object_new (G_VFS_TYPE_DNS_SD_RESOLVER,
843 "service-name", service_name,
844 "service-type", service_type,
845 "domain", domain,
846 "required-txt-keys", required_txt_keys,
847 NULL));
848 }
849
850 static int
safe_strcmp(const char * a,const char * b)851 safe_strcmp (const char *a, const char *b)
852 {
853 if (a == NULL)
854 a = "";
855 if (b == NULL)
856 b = "";
857 return strcmp (a, b);
858 }
859
860 static gboolean
strv_equal(char ** a,char ** b)861 strv_equal (char **a, char **b)
862 {
863 static char *dummy[1] = {NULL};
864 int n;
865 gboolean ret;
866
867 if (a == NULL)
868 a = dummy;
869 if (b == NULL)
870 b = dummy;
871
872 ret = FALSE;
873
874 if (g_strv_length (a) != g_strv_length (b))
875 goto out;
876
877 for (n = 0; a[n] != NULL && b[n] != NULL; n++)
878 {
879 if (strcmp (a[n], b[n]) != 0)
880 goto out;
881 }
882
883 ret = TRUE;
884
885 out:
886 return ret;
887
888 }
889
890 static gboolean
has_required_txt_keys(GVfsDnsSdResolver * resolver)891 has_required_txt_keys (GVfsDnsSdResolver *resolver)
892 {
893 gboolean ret;
894 int n;
895 char *value;
896
897 ret = FALSE;
898
899 if (resolver->required_txt_keys_broken_out != NULL)
900 {
901 for (n = 0; resolver->required_txt_keys_broken_out[n] != NULL; n++)
902 {
903 value = g_vfs_dns_sd_resolver_lookup_txt_record (resolver,
904 resolver->required_txt_keys_broken_out[n]);
905 if (value == NULL)
906 {
907 /* key is missing */
908 goto out;
909 }
910 g_free (value);
911 }
912 }
913
914 ret = TRUE;
915
916 out:
917 return ret;
918 }
919
920 static void
set_avahi_data(GVfsDnsSdResolver * resolver,const char * host_name,AvahiProtocol protocol,const AvahiAddress * a,AvahiIfIndex interface,uint16_t port,AvahiStringList * txt)921 set_avahi_data (GVfsDnsSdResolver *resolver,
922 const char *host_name,
923 AvahiProtocol protocol,
924 const AvahiAddress *a,
925 AvahiIfIndex interface,
926 uint16_t port,
927 AvahiStringList *txt)
928 {
929 char *address;
930 gchar ifname[IF_NAMESIZE] = {0};
931 gboolean changed;
932 AvahiStringList *l;
933 GPtrArray *p;
934 char **txt_records;
935 gboolean is_resolved;
936
937 changed = FALSE;
938
939 if (resolver_supports_mdns)
940 {
941 address = g_strdup (host_name);
942 }
943 else
944 {
945 char aa[128];
946
947 avahi_address_snprint (aa, sizeof(aa), a);
948 if (protocol == AVAHI_PROTO_INET6)
949 {
950 /* an ipv6 address, follow RFC 2732 */
951 address = g_strdup_printf ("[%s]", aa);
952 }
953 else
954 {
955 address = g_strdup (aa);
956 }
957 }
958
959 if (safe_strcmp (resolver->address, address) != 0)
960 {
961 g_free (resolver->address);
962 resolver->address = g_strdup (address);
963 g_object_notify (G_OBJECT (resolver), "address");
964 changed = TRUE;
965 }
966
967 g_free (address);
968
969 if_indextoname (interface, ifname);
970 if (safe_strcmp (resolver->interface, ifname) != 0)
971 {
972 g_free (resolver->interface);
973 resolver->interface = g_strdup (ifname);
974 g_object_notify (G_OBJECT (resolver), "interface");
975 changed = TRUE;
976 }
977
978 if (resolver->port != port)
979 {
980 resolver->port = port;
981 g_object_notify (G_OBJECT (resolver), "port");
982 changed = TRUE;
983 }
984
985 p = g_ptr_array_new ();
986 for (l = txt; l != NULL; l = avahi_string_list_get_next (l))
987 {
988 g_ptr_array_add (p, g_strdup ((const char *) l->text));
989 }
990 g_ptr_array_add (p, NULL);
991 txt_records = (char **) g_ptr_array_free (p, FALSE);
992
993 if (strv_equal (resolver->txt_records, txt_records))
994 {
995 g_strfreev (txt_records);
996 }
997 else
998 {
999 g_strfreev (resolver->txt_records);
1000 resolver->txt_records = txt_records;
1001 g_object_notify (G_OBJECT (resolver), "txt-records");
1002 changed = TRUE;
1003 }
1004
1005 is_resolved = has_required_txt_keys (resolver);
1006
1007 if (is_resolved != resolver->is_resolved)
1008 {
1009 resolver->is_resolved = is_resolved;
1010 g_object_notify (G_OBJECT (resolver), "is-resolved");
1011 changed = TRUE;
1012 }
1013
1014 if (changed)
1015 g_signal_emit (resolver, signals[CHANGED], 0);
1016 }
1017
1018 static void
clear_avahi_data(GVfsDnsSdResolver * resolver)1019 clear_avahi_data (GVfsDnsSdResolver *resolver)
1020 {
1021 gboolean changed;
1022
1023 changed = FALSE;
1024
1025 if (resolver->is_resolved)
1026 {
1027 resolver->is_resolved = FALSE;
1028 g_object_notify (G_OBJECT (resolver), "is-resolved");
1029 changed = TRUE;
1030 }
1031
1032 if (resolver->address != NULL)
1033 {
1034 g_free (resolver->address);
1035 resolver->address = NULL;
1036 g_object_notify (G_OBJECT (resolver), "address");
1037 changed = TRUE;
1038 }
1039
1040 if (resolver->interface != NULL)
1041 {
1042 g_free (resolver->interface);
1043 resolver->interface = NULL;
1044 g_object_notify (G_OBJECT (resolver), "interface");
1045 changed = TRUE;
1046 }
1047
1048 if (resolver->port != 0)
1049 {
1050 resolver->port = 0;
1051 g_object_notify (G_OBJECT (resolver), "port");
1052 changed = TRUE;
1053 }
1054
1055 if (resolver->txt_records != NULL)
1056 {
1057 resolver->txt_records = NULL;
1058 g_object_notify (G_OBJECT (resolver), "txt-records");
1059 changed = TRUE;
1060 }
1061
1062 if (changed)
1063 g_signal_emit (resolver, signals[CHANGED], 0);
1064 }
1065
1066 static void
service_resolver_cb(AvahiServiceResolver * avahi_resolver,AvahiIfIndex interface,AvahiProtocol protocol,AvahiResolverEvent event,const char * name,const char * type,const char * domain,const char * host_name,const AvahiAddress * a,uint16_t port,AvahiStringList * txt,AvahiLookupResultFlags flags,void * user_data)1067 service_resolver_cb (AvahiServiceResolver *avahi_resolver,
1068 AvahiIfIndex interface,
1069 AvahiProtocol protocol,
1070 AvahiResolverEvent event,
1071 const char *name,
1072 const char *type,
1073 const char *domain,
1074 const char *host_name,
1075 const AvahiAddress *a,
1076 uint16_t port,
1077 AvahiStringList *txt,
1078 AvahiLookupResultFlags flags,
1079 void *user_data)
1080 {
1081 GVfsDnsSdResolver *resolver = G_VFS_DNS_SD_RESOLVER (user_data);
1082
1083 switch (event)
1084 {
1085 case AVAHI_RESOLVER_FOUND:
1086 set_avahi_data (resolver,
1087 host_name,
1088 protocol,
1089 a,
1090 interface,
1091 port,
1092 txt);
1093 break;
1094
1095 case AVAHI_RESOLVER_FAILURE:
1096 clear_avahi_data (resolver);
1097 break;
1098 }
1099 }
1100
1101
1102 typedef struct
1103 {
1104 GVfsDnsSdResolver *resolver;
1105 guint timeout_id;
1106 } ResolveData;
1107
1108 static void service_resolver_changed (GVfsDnsSdResolver *resolver, GTask *task);
1109
1110 static void
resolve_data_free(ResolveData * data)1111 resolve_data_free (ResolveData *data)
1112 {
1113 if (data->timeout_id > 0)
1114 g_source_remove (data->timeout_id);
1115 g_signal_handlers_disconnect_by_func (data->resolver, service_resolver_changed, data);
1116 g_free (data);
1117 }
1118
1119 static void
service_resolver_changed(GVfsDnsSdResolver * resolver,GTask * task)1120 service_resolver_changed (GVfsDnsSdResolver *resolver,
1121 GTask *task)
1122 {
1123 ResolveData *data = g_task_get_task_data (task);
1124
1125 if (resolver->is_resolved)
1126 {
1127 g_task_return_boolean (task, TRUE);
1128 g_object_unref (task);
1129 }
1130 else
1131 {
1132 if (data->resolver->address != NULL)
1133 {
1134 /* keep going until timeout if we're missing TXT records */
1135 }
1136 else
1137 {
1138 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
1139 /* Translators:
1140 * - the first %s refers to the service type
1141 * - the second %s refers to the service name
1142 * - the third %s refers to the domain
1143 */
1144 _("Error resolving “%s” service “%s” on domain “%s”"),
1145 data->resolver->service_type,
1146 data->resolver->service_name,
1147 data->resolver->domain);
1148 g_object_unref (task);
1149 }
1150 }
1151 }
1152
1153 static gboolean
service_resolver_timed_out(GTask * task)1154 service_resolver_timed_out (GTask *task)
1155 {
1156 ResolveData *data = g_task_get_task_data (task);
1157
1158 if (data->resolver->address != NULL)
1159 {
1160 /* special case if one of the required TXT records are missing */
1161 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
1162 /* Translators:
1163 * - the first %s refers to the service type
1164 * - the second %s refers to the service name
1165 * - the third %s refers to the domain
1166 * - the fourth %s refers to the required TXT keys
1167 */
1168 _("Error resolving “%s” service “%s” on domain “%s”. "
1169 "One or more TXT records are missing. Keys required: “%s”."),
1170 data->resolver->service_type,
1171 data->resolver->service_name,
1172 data->resolver->domain,
1173 data->resolver->required_txt_keys);
1174 }
1175 else
1176 {
1177 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
1178 /* Translators:
1179 * - the first %s refers to the service type
1180 * - the second %s refers to the service name
1181 * - the third %s refers to the domain
1182 */
1183 _("Timed out resolving “%s” service “%s” on domain “%s”"),
1184 data->resolver->service_type,
1185 data->resolver->service_name,
1186 data->resolver->domain);
1187 }
1188
1189 data->timeout_id = 0;
1190 g_object_unref (task);
1191
1192 return FALSE;
1193 }
1194
1195 gboolean
g_vfs_dns_sd_resolver_resolve_finish(GVfsDnsSdResolver * resolver,GAsyncResult * res,GError ** error)1196 g_vfs_dns_sd_resolver_resolve_finish (GVfsDnsSdResolver *resolver,
1197 GAsyncResult *res,
1198 GError **error)
1199 {
1200 g_return_val_if_fail (g_task_is_valid (res, resolver), FALSE);
1201 g_return_val_if_fail (g_async_result_is_tagged (res, g_vfs_dns_sd_resolver_resolve), FALSE);
1202
1203 return g_task_propagate_boolean (G_TASK (res), error);
1204 }
1205
1206 void
g_vfs_dns_sd_resolver_resolve(GVfsDnsSdResolver * resolver,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1207 g_vfs_dns_sd_resolver_resolve (GVfsDnsSdResolver *resolver,
1208 GCancellable *cancellable,
1209 GAsyncReadyCallback callback,
1210 gpointer user_data)
1211 {
1212 ResolveData *data;
1213 GTask *task;
1214
1215 g_return_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver));
1216
1217 task = g_task_new (resolver, cancellable, callback, user_data);
1218 g_task_set_source_tag (task, g_vfs_dns_sd_resolver_resolve);
1219
1220 if (resolver->service_type == NULL)
1221 {
1222 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
1223 _("Error initializing Avahi resolver"));
1224 g_object_unref (task);
1225 return;
1226 }
1227
1228 if (resolver->is_resolved)
1229 {
1230 g_task_return_boolean (task, TRUE);
1231 g_object_unref (task);
1232 return;
1233 }
1234
1235 ensure_avahi_resolver (resolver);
1236
1237 data = g_new0 (ResolveData, 1);
1238 data->resolver = resolver;
1239 data->timeout_id = g_timeout_add (resolver->timeout_msec,
1240 (GSourceFunc) service_resolver_timed_out,
1241 task);
1242
1243 g_task_set_task_data (task, data, (GDestroyNotify) resolve_data_free);
1244
1245 g_signal_connect (resolver,
1246 "changed",
1247 (GCallback) service_resolver_changed,
1248 task);
1249 }
1250
1251
1252 typedef struct
1253 {
1254 GMutex mutex;
1255 GCond cond;
1256 gboolean done;
1257 GError *error;
1258 gboolean ret;
1259 } ResolveDataSync;
1260
1261 static void
resolve_sync_cb(GVfsDnsSdResolver * resolver,GAsyncResult * res,ResolveDataSync * data)1262 resolve_sync_cb (GVfsDnsSdResolver *resolver,
1263 GAsyncResult *res,
1264 ResolveDataSync *data)
1265 {
1266 data->ret = g_vfs_dns_sd_resolver_resolve_finish (resolver,
1267 res,
1268 &(data->error));
1269 g_mutex_lock (&data->mutex);
1270 data->done = TRUE;
1271 g_cond_signal (&data->cond);
1272 g_mutex_unlock (&data->mutex);
1273 }
1274
1275 /* Do not call from the global main loop thread. */
1276 gboolean
g_vfs_dns_sd_resolver_resolve_sync(GVfsDnsSdResolver * resolver,GCancellable * cancellable,GError ** error)1277 g_vfs_dns_sd_resolver_resolve_sync (GVfsDnsSdResolver *resolver,
1278 GCancellable *cancellable,
1279 GError **error)
1280 {
1281 ResolveDataSync *data;
1282 gboolean ret;
1283
1284 g_return_val_if_fail (G_VFS_IS_DNS_SD_RESOLVER (resolver), FALSE);
1285
1286 data = g_new0 (ResolveDataSync, 1);
1287 g_cond_init (&data->cond);
1288 g_mutex_init (&data->mutex);
1289
1290 g_mutex_lock (&data->mutex);
1291 g_vfs_dns_sd_resolver_resolve (resolver,
1292 cancellable,
1293 (GAsyncReadyCallback) resolve_sync_cb,
1294 data);
1295
1296 while (!data->done)
1297 g_cond_wait (&data->cond, &data->mutex);
1298 g_mutex_unlock (&data->mutex);
1299
1300 ret = data->ret;
1301 if (data->error != NULL)
1302 g_propagate_error (error, data->error);
1303
1304 g_mutex_clear (&data->mutex);
1305 g_cond_clear (&data->cond);
1306 g_free (data);
1307
1308 return ret;
1309 }
1310