Logo Search packages:      
Sourcecode: gnome-vfs2 version File versions  Download package

gnome-vfs-dns-sd.c

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gnome-vfs-dns-sd.c - DNS-SD functions

   Copyright (C) 2004 Red Hat, Inc

   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Author: Alexander Larsson <alexl@redhat.com>
*/

#include <config.h>

#if HAVE_UNISTD_H
# include <unistd.h>
#endif /* #if HAVE_UNISTD_H */
#include <sys/time.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <string.h>
#include "gnome-vfs-dns-sd.h"
#include <gconf/gconf-client.h>
#include <unistd.h>

#ifdef HAVE_AVAHI
#include <avahi-client/client.h>
#include <avahi-client/lookup.h>
#include <avahi-common/error.h>
#include <avahi-common/simple-watch.h>
#include <avahi-common/timeval.h>
#include <avahi-glib/glib-watch.h>
#endif 

#ifdef HAVE_HOWL
/* Need to work around howl exporting its config file... */
#undef PACKAGE
#undef VERSION
#include <howl.h>
#endif

#define PATH_GCONF_GNOME_VFS_DNS_SD "/system/dns_sd"
#define PATH_GCONF_GNOME_VFS_DNS_SD_EXTRA_DOMAINS "/system/dns_sd/extra_domains"

#define DNS_REPLY_SIZE (64*1024)


/* Some systems don't those namespaced constants defined, see 
 * http://bugzilla.gnome.org/show_bug.cgi?id=162289
 * I'm assuming that if 1 constant isn't defined, then all the others
 * are missing too, please file a bug if that's not the case on your system
 */
#ifndef NS_MAXDNAME
#define NS_MAXDNAME MAXDNAME
#define NS_HFIXEDSZ HFIXEDSZ
#define ns_c_in     C_IN
#define ns_t_any    T_ANY
#define ns_t_srv    T_SRV
#define ns_t_txt    T_TXT
#define ns_t_ptr    T_PTR
#endif /* NS_MAXDNAME */

/* Unicast DNS browsing: */

typedef struct {
      guint16 id;
      guint16 flags;
      guint16 qdcount;
      guint16 ancount;
      guint16 nscount;
      guint16 arcount;
} dns_message_header;

typedef struct {
      char name[NS_MAXDNAME];
      guint16 type;
      guint16 class;
      guint32 ttl;
      guint16 rdlength;
} dns_message_rr;

/* Normally, this would be autogenerated by glib-mkenums, but in this case it
   has no way to guess correctly. */
GType
gnome_vfs_dns_sd_service_status_get_type (void)
{
  static GType etype = 0;
  if (etype == 0) {
    static const GEnumValue values[] = {
      { GNOME_VFS_DNS_SD_SERVICE_ADDED, "GNOME_VFS_DNS_SD_SERVICE_ADDED", "added" },
      { GNOME_VFS_DNS_SD_SERVICE_REMOVED, "GNOME_VFS_DNS_SD_SERVICE_REMOVED", "removed" },
      { 0, NULL, NULL }
    };
    etype = g_enum_register_static ("GnomeVFSDNSSDServiceStatus", values);
  }
  return etype;
}

static GHashTable *
decode_txt_record (char *raw_txt,
               int raw_txt_len)
{
      GHashTable *hash;
      int i;
      int len;
      char *key, *value, *end;
      char *key_dup, *value_dup;

      if (raw_txt == NULL)
            return NULL;
      
      hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

      i = 0;
      while (i < raw_txt_len) {
            len = raw_txt[i++];
            
            if (i + len > raw_txt_len) {
                  break;
            }
            
            if (len == 0) {
                  continue;
            }
            
            key = &raw_txt[i];
            end = &raw_txt[i + len];
            i += len;

            if (*key == '=') {
                  /* 6.4 - silently ignore keys starting with = */
                  continue;
            }
            
            value = memchr (key, '=', len);
            if (value) {
                  key_dup = g_strndup (key, value - key);
                  value++; /* Skip '=' */
                  value_dup = g_strndup (value, end - value);
            } else {
                  key_dup = g_strndup (key, len);
                  value_dup = NULL;
            }
            if (!g_hash_table_lookup_extended (hash,
                                       key_dup,
                                       NULL, NULL)) {
                  g_hash_table_insert (hash,
                                   key_dup,
                                   value_dup);
            } else {
                  g_free (key_dup);
                  g_free (value_dup);
            }
      }

      return hash;
}


static guint16
decode_16 (unsigned char *p)
{
      return p[0] << 8 | p[1];
}

static guint32
decode_32 (unsigned char *p)
{
      return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
}

static void
split_service_instance (char *name,
                  char *service,
                  char *type,
                  char *domain)
{
      int i, n_dots;

      i = 0;
      while (*name != 0) {
            if (*name == '.') {
                  name++;
                  break;
            }
            if (*name == '\\') {
                  name++;
                  if (g_ascii_isdigit (*name)) {
                        if (g_ascii_isdigit (name[1]) &&
                            g_ascii_isdigit (name[2])) {
                              service[i++] =
                                    g_ascii_digit_value (name[0]) * 100 +
                                    g_ascii_digit_value (name[1]) * 10 +
                                    g_ascii_digit_value (name[2]);
                              name += 3;
                        }
                  } else if (*name != 0) {
                        service[i++] = *name++;
                  }
            } else {
                  service[i++] = *name++;
            }
      }
      service[i] = 0;
      
      i = 0;
      n_dots = 0;
      while (*name != 0) {
            if (*name == '.') {
                  n_dots++;
            }
            if (n_dots == 2) {
                  name++;
                  break;
            }
            type[i++] = *name++;
      }
      type[i] = 0;
      
      i = 0;
      while (*name != 0) {
            domain[i++] = *name++;
      }
      domain[i] = 0;
}

static gboolean
is_valid_dns_sd_type (const char *type)
{
      return type[0] == '_' &&
            (g_str_has_suffix (type, "._tcp") ||
             g_str_has_suffix (type, "._udp"));
}

static int
parse_header (unsigned char *reply, int reply_len,
            unsigned char *p,
            dns_message_header *header)
{
      if (reply_len < NS_HFIXEDSZ)
            return -1;

      header->id = decode_16 (p); p += 2;
      header->flags = decode_16 (p); p += 2;
      header->qdcount = decode_16 (p); p += 2;
      header->ancount = decode_16 (p); p += 2;
      header->nscount = decode_16 (p); p += 2;
      header->arcount = decode_16 (p); p += 2;

      return NS_HFIXEDSZ;
}


static int
parse_qs (unsigned char *reply, int reply_len,
        unsigned char *p,
        char *name, int name_size,
        int *qtype, int *qclass)
{
      int len;
      unsigned char *start, *end;
      
      start = p;
      end = reply + reply_len;
      
      if (p >= end)
            return -1;
      len = dn_expand (reply, reply + reply_len, p, name, name_size);
      if (len < 0)
            return -1;
      
      p += len;
      
      if (p + 4 > end)
            return -1;
      
      *qtype = decode_16 (p); p += 2;
      *qclass = decode_16 (p); p += 2;
      
      return p - start;
}

static int
parse_rr (unsigned char *reply, int reply_len,
        unsigned char *p,
        dns_message_rr *rr)
{
      int len;
      unsigned char *start, *end;
      
      start = p;
      end = reply + reply_len;
      
      if (p >= end)
            return -1;
      len = dn_expand (reply, reply + reply_len, p, rr->name, sizeof (rr->name));
      if (len < 0)
            return -1;
      p += len;
      
      if (p + 10 > end)
            return -1;
      
      rr->type = decode_16 (p); p += 2;
      rr->class = decode_16 (p); p += 2;
      rr->ttl = decode_32 (p); p += 4;
      rr->rdlength = decode_16 (p); p += 2;
      
      if (p + rr->rdlength > end)
            return -1;

      return p - start;
}

static GnomeVFSResult
unicast_list_domains_sync (const char *domain,
                     GList **domains)
{
      int reply_len, len, i;
      unsigned char reply[DNS_REPLY_SIZE];
      unsigned char *p;
      char *searchdomain;
      char name[NS_MAXDNAME];
      dns_message_header header;
      dns_message_rr rr;
      int res;
      GList *l;

      *domains = NULL;
      
      res = res_init ();
      if (res != 0) {
            return GNOME_VFS_ERROR_INTERNAL;
      }
      
      /* Use TCP to support large queries */
      _res.options |= RES_USEVC;
      searchdomain = g_strconcat ("_browse._dns-sd._udp.", domain, NULL);
      reply_len = res_search (searchdomain, ns_c_in, ns_t_ptr,
                        reply, sizeof (reply));
      g_free (searchdomain);
      if (reply_len == -1) {
            return GNOME_VFS_ERROR_GENERIC;
      }
  
      /* Parse out query */
      p = reply;
      
      len = parse_header (reply, reply_len, p,
                      &header);
      if (len < 0) {
            goto error;
      }
      p += len;
      
      if ((header.flags & (1 << 15)) == 0) {
            /* Not a reply */
            goto error;
      }
      
      if (header.flags & (1 << 9)) {
            /* Truncated */
            goto error;
      }

      for (i = 0; i < header.qdcount; i++) {
            int qtype, qclass;
            
            len = parse_qs (reply, reply_len, p,
                        name, sizeof(name),
                        &qtype, &qclass);
            if (len < 0) 
                  goto error;
            p += len;
      }

      for (i = 0; i < header.ancount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            if (rr.type == ns_t_ptr) {
                  len = dn_expand (reply, reply + reply_len, p, name, sizeof(name));
                  if (len < 0) 
                        goto error;

                  *domains = g_list_prepend (*domains, g_strdup (name));
            }
            
            p += rr.rdlength;
      }
      
      for (i = 0; i < header.nscount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            p += rr.rdlength;
      }
      
      for (i = 0; i < header.arcount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            p += rr.rdlength;
      }

      return GNOME_VFS_OK;
      
 error:
      for (l = *domains; l != NULL; l = l->next) {
            g_free (l->data);
      }
      g_list_free (*domains);
      *domains = NULL;
      
      return GNOME_VFS_ERROR_GENERIC;
}


static GnomeVFSResult
unicast_browse_sync (const char *domain, const char *type,
                 int *num_services,
                 GnomeVFSDNSSDService **services)
{
      int reply_len, len, i;
      unsigned char reply[DNS_REPLY_SIZE];
      unsigned char *p;
      char *searchdomain;
      char name[NS_MAXDNAME];
      dns_message_header header;
      dns_message_rr rr;
      GArray *array;
      GnomeVFSDNSSDService *service;
      int res;

      array = NULL;
      
      res = res_init ();
      if (res != 0) {
            return GNOME_VFS_ERROR_INTERNAL;
      }
      
      /* Use TCP to support large queries */
      _res.options |= RES_USEVC;
      searchdomain = g_strconcat (type, ".", domain, NULL);
      reply_len = res_search (searchdomain, ns_c_in, ns_t_ptr,
                        reply, sizeof (reply));
      g_free (searchdomain);
      if (reply_len == -1) {
            return GNOME_VFS_ERROR_GENERIC;
      }
  
      /* Parse out query */
      p = reply;
      
      len = parse_header (reply, reply_len, p,
                      &header);
      if (len < 0) {
            goto error;
      }
      p += len;
      
      if ((header.flags & (1 << 15)) == 0) {
            /* Not a reply */
            goto error;
      }
      
      if (header.flags & (1 << 9)) {
            /* Truncated */
            goto error;
      }

      for (i = 0; i < header.qdcount; i++) {
            int qtype, qclass;
            
            len = parse_qs (reply, reply_len, p,
                        name, sizeof(name),
                        &qtype, &qclass);
            if (len < 0) 
                  goto error;
            p += len;
      }

      array = g_array_new (FALSE, FALSE, sizeof (GnomeVFSDNSSDService));

      for (i = 0; i < header.ancount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            if (rr.type == ns_t_ptr) {
                  GnomeVFSDNSSDService service;
                  char ptr_service[NS_MAXDNAME];
                  char ptr_type[NS_MAXDNAME];
                  char ptr_domain[NS_MAXDNAME];
                  
                  len = dn_expand (reply, reply + reply_len, p, name, sizeof(name));
                  if (len < 0) 
                        goto error;
                  
                  split_service_instance (name, ptr_service, ptr_type, ptr_domain);

                  if (is_valid_dns_sd_type (ptr_type)) {
                        service.name = g_strdup (ptr_service);
                        service.type = g_strdup (ptr_type);
                        service.domain = g_strdup (ptr_domain);

                        g_array_append_val(array, service);
                  }
            }
            
            p += rr.rdlength;
      }
      
      for (i = 0; i < header.nscount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            p += rr.rdlength;
      }
      
      for (i = 0; i < header.arcount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            p += rr.rdlength;
      }

      *num_services = array->len;
      *services = (GnomeVFSDNSSDService *)g_array_free (array, FALSE);
      
      return GNOME_VFS_OK;
      
 error:
      for (i = 0; i < array->len; i++) {
            service = &g_array_index (array, GnomeVFSDNSSDService, i);
            g_free (service->name);
            g_free (service->type);
            g_free (service->domain);
      }
      g_array_free (array, TRUE);
      
      return GNOME_VFS_ERROR_GENERIC;
}

static char *
service_to_dns_name (const char *name, const char *type, const char *domain)
{
      GString *string;
      const char *p;

      string = g_string_new (NULL);

      p = name;

      while (*p) {
            if (*p == '\\') 
                  g_string_append (string, "\\\\");
            else if (*p == '.') 
                  g_string_append (string, "\\.");
            else
                  g_string_append_c (string, *p);
            p++;
      }
      g_string_append_c (string, '.');
      g_string_append (string, type);
      g_string_append_c (string, '.');
      g_string_append (string, domain);

      return g_string_free (string, FALSE);
}

static GnomeVFSResult
unicast_resolve_sync (const char *name,
                  const char *type,
                  const char *domain,
                  char **host,
                  int *port_out,
                  int *text_raw_len_out,
                  char **text_raw_out)
{
      int reply_len, len, i;
      unsigned char reply[DNS_REPLY_SIZE];
      unsigned char *p;
      char dnsname[NS_MAXDNAME];
      dns_message_header header;
      dns_message_rr rr;
      char *full_name;
      int res;

      *host = NULL;
      *port_out = 0;
      *text_raw_len_out = 0;
      *text_raw_out = NULL;
      
      res = res_init ();
      if (res != 0) {
            return GNOME_VFS_ERROR_INTERNAL;
      }

      /* Use TCP to support large queries */
      _res.options |= RES_USEVC;
  
      full_name = service_to_dns_name (name, type, domain);
      reply_len = res_search (full_name, ns_c_in, ns_t_any,
                        reply, sizeof (reply));
      g_free (full_name);
      if (reply_len == -1) {
            return GNOME_VFS_ERROR_GENERIC;
      }
  
      /* Parse out query */
      p = reply;
      
      len = parse_header (reply, reply_len, p,
                      &header);
      if (len < 0)
            goto error;
      p += len;
      
      if ((header.flags & (1 << 15)) == 0) {
            /* Not a reply */
            goto error;
      }
      
      if (header.flags & (1 << 9)) {
            /* Truncated */
            g_warning ("dns-sd reply truncated!\n");
            goto error;
      }
      
      for (i = 0; i < header.qdcount; i++) {
            int qtype, qclass;
            
            len = parse_qs (reply, reply_len, p,
                        dnsname, sizeof(dnsname),
                        &qtype, &qclass);
            if (len < 0) 
                  goto error;
            p += len;
      }
      
      for (i = 0; i < header.ancount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            if (rr.type == ns_t_srv) {
                  unsigned char *pp;
                  int priority, weight, port;
                  
                  pp = p;
                  
                  priority = decode_16 (pp); pp += 2;
                  weight = decode_16 (pp); pp += 2;
                  port = decode_16 (pp); pp += 2;
                  
                  len = dn_expand (reply, reply + reply_len, pp, dnsname, sizeof(dnsname));
                  if (len < 0) 
                        goto error;

                  /* TODO: look at prio and weigth. For now use the first */
                  if (*host == NULL) {
                        *host = g_strdup (dnsname);
                        *port_out = port;
                  }
            }
            
            if (rr.type == ns_t_txt) {
                  *text_raw_out = g_memdup (p, rr.rdlength);
                  *text_raw_len_out = rr.rdlength;
            }
            
            p += rr.rdlength;
      }
      
      for (i = 0; i < header.nscount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            p += rr.rdlength;
            
      }
      
      for (i = 0; i < header.arcount; i++) {
            len = parse_rr (reply, reply_len, p, &rr);
            if (len < 0) 
                  goto error;
            p += len;
            
            p += rr.rdlength;
      }

      return GNOME_VFS_OK;
      
 error:
      g_free (*host);
      *host = NULL;
      g_free (*text_raw_out);
      *text_raw_out = NULL;
      
      return GNOME_VFS_ERROR_GENERIC;
}


/* multicast DNS functions */

#ifdef HAVE_AVAHI
static AvahiClient *global_client = NULL;
static gboolean avahi_initialized = FALSE;

static AvahiClient *get_global_avahi_client (void);

/* Callback for state changes on the Client */
static void
avahi_client_callback (AvahiClient *client, AvahiClientState state, void *userdata)
{
      if (state == AVAHI_CLIENT_FAILURE) {
            if (avahi_client_errno (client) == AVAHI_ERR_DISCONNECTED) {
                  /* Destroy old client */
                  avahi_client_free (client);
                  global_client = NULL;
                  avahi_initialized = FALSE;

                  /* Reconnect */
                  get_global_avahi_client ();
            }
      }
}

static AvahiClient *
get_global_avahi_client (void) {
      static AvahiGLibPoll *glib_poll = NULL;
      int error;

      if (!avahi_initialized) {
            if (glib_poll == NULL) {
                  glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT);
            }

            /* Create a new AvahiClient instance */
            global_client = avahi_client_new (avahi_glib_poll_get (glib_poll),
                                      AVAHI_CLIENT_NO_FAIL,
                                      avahi_client_callback,
                                      glib_poll,
                                      &error);

            if (global_client == NULL) {    
                  /* Print out the error string */
                  g_warning ("Error initializing Avahi: %s", avahi_strerror (error));
                  avahi_glib_poll_free (glib_poll);
                  glib_poll = NULL;
                  return NULL;
            }
            avahi_initialized = TRUE;
      }

      return global_client;
}
#endif

#ifdef HAVE_HOWL

static gboolean
howl_input (GIOChannel  *io_channel,
          GIOCondition cond,
          gpointer     callback_data)
{
      sw_discovery session;
      sw_salt salt;
      session = callback_data;

      if (sw_discovery_salt (session, &salt) == SW_OKAY) {
            sw_salt_lock (salt);
            sw_discovery_read_socket (session);
            sw_salt_unlock (salt);
      }
      return TRUE;
}


static void
set_up_howl_session (sw_discovery session)
{
      int fd;
      GIOChannel *channel;

      fd = sw_discovery_socket (session);

      channel = g_io_channel_unix_new (fd);
      g_io_add_watch (channel,
                  G_IO_IN,
                  howl_input, session);
      g_io_channel_unref (channel);
}

static sw_discovery
get_global_howl_session (void) {
      static sw_discovery global_session = NULL;
      static gboolean initialized = FALSE;

      if (!initialized) {
            if (sw_discovery_init (&global_session) != SW_OKAY) {
                  g_warning ("get_global_howl_session - howl init failed\n");
                  return NULL;
            }
            set_up_howl_session (global_session);
            initialized = TRUE;
      }

      return global_session;
}

#endif /* HAVE_HOWL */

struct GnomeVFSDNSSDBrowseHandle {
      char *domain;
      char *type;
      GnomeVFSDNSSDBrowseCallback callback;
      gpointer callback_data;
      GDestroyNotify callback_data_destroy_func;

      gboolean is_local;
      gboolean cancelled;

      /* multicast: */

#ifdef HAVE_AVAHI
      AvahiServiceBrowser *avahi_sb;
#endif

#ifdef HAVE_HOWL
      sw_discovery_oid howl_id;
#endif
      
      /* unicast data: */
      int n_services;
      GnomeVFSDNSSDService *services;
      GnomeVFSResult res;
      gboolean finished;
};


static void
free_browse_handle (GnomeVFSDNSSDBrowseHandle *handle)
{
      int i;
      
      g_free (handle->domain);
      g_free (handle->type);
      
      for (i = 0; i < handle->n_services; i++) {
            g_free (handle->services[i].name);
            g_free (handle->services[i].type);
            g_free (handle->services[i].domain);
      }
      
      g_free (handle->services);

      if (handle->callback_data_destroy_func != NULL)
            handle->callback_data_destroy_func (handle->callback_data);
      
      g_free (handle);
}

static gboolean
unicast_browse_idle (gpointer data)
{
      GnomeVFSDNSSDBrowseHandle *handle;
      int i;
      
      handle = data;
      
      if (!handle->cancelled &&
          handle->res == GNOME_VFS_OK) {
            for (i = 0; i < handle->n_services; i++) {
                  handle->callback (data,
                                GNOME_VFS_DNS_SD_SERVICE_ADDED,
                                &handle->services[i],
                                handle->callback_data);
            }
      }

      handle->finished = TRUE;
      
      if (handle->cancelled)
            free_browse_handle (handle);
      
      return FALSE;
}


static gpointer
unicast_browse_thread (gpointer data)
{
      GnomeVFSDNSSDBrowseHandle *handle;
      handle = data;
      
      handle->res = unicast_browse_sync (handle->domain,
                                 handle->type,
                                 &handle->n_services,
                                 &handle->services);
      g_idle_add (unicast_browse_idle,
                handle);
      return NULL;
}

#ifdef HAVE_AVAHI
static void 
avahi_browse_callback (AvahiServiceBrowser *b,
                   AvahiIfIndex interface,
                   AvahiProtocol protocol,
                   AvahiBrowserEvent event,
                   const char *name,
                   const char *type,
                   const char *domain,
                   AvahiLookupResultFlags flags,
                   void *userdata)
{
      GnomeVFSDNSSDBrowseHandle *handle;
      GnomeVFSDNSSDService service;
      handle = userdata;
    
      service.name = (char *)name;
      service.type = (char *)type;
      service.domain = (char *)domain;
      
      if (event == AVAHI_BROWSER_FAILURE ||
          event == AVAHI_BROWSER_ALL_FOR_NOW ||
          event == AVAHI_BROWSER_CACHE_EXHAUSTED) {
            return;
      }
      
      if (!handle->cancelled) {
            handle->callback (handle,
                          (event == AVAHI_BROWSER_NEW) ? GNOME_VFS_DNS_SD_SERVICE_ADDED : GNOME_VFS_DNS_SD_SERVICE_REMOVED,
                          &service,
                          handle->callback_data);
      }
}
#endif

#ifdef HAVE_HOWL

struct howl_browse_idle_data {
      GnomeVFSDNSSDBrowseHandle *handle;
      GnomeVFSDNSSDServiceStatus status;
      GnomeVFSDNSSDService service;
      
};


static gboolean
howl_browse_idle (gpointer data)
{
      struct howl_browse_idle_data *idle_data;
      GnomeVFSDNSSDBrowseHandle *handle;

      idle_data = data;
      handle = idle_data->handle;

      if (handle->cancelled)
            return FALSE;

      handle->callback (handle,
                    idle_data->status,
                    &idle_data->service,
                    handle->callback_data);
      
      return FALSE;
}

static void
browse_idle_data_free (struct howl_browse_idle_data *idle_data)
{
      g_free (idle_data->service.name);
      g_free (idle_data->service.type);
      g_free (idle_data->service.domain);
      g_free (idle_data);
}

static gboolean
free_browse_handle_idle (gpointer data)
{
      free_browse_handle (data);
      return FALSE;
}

static sw_result
howl_browse_reply (sw_discovery                 discovery,
               sw_discovery_oid             oid,
               sw_discovery_browse_status   status,
               sw_uint32                  interface_index,
               sw_const_string              name,
               sw_const_string              type,
               sw_const_string              domain,
               sw_opaque                    extra)
{
      GnomeVFSDNSSDBrowseHandle *handle;
      struct howl_browse_idle_data *idle_data;
      int len;

      handle = extra;
      
      if (status == SW_DISCOVERY_BROWSE_RELEASE) {
            /* free in an idle to make sure the other idles are done,
               and to give sane environment for destroy callback */
            g_idle_add (free_browse_handle_idle, handle);
            return SW_OKAY;
      }
      
      if (handle->cancelled)
            return SW_OKAY;

      idle_data = g_new (struct howl_browse_idle_data, 1);
      idle_data->handle = handle;
      
      if (status == SW_DISCOVERY_BROWSE_ADD_SERVICE) {
            idle_data->status = GNOME_VFS_DNS_SD_SERVICE_ADDED;
      } else if (status == SW_DISCOVERY_BROWSE_REMOVE_SERVICE) {
            idle_data->status = GNOME_VFS_DNS_SD_SERVICE_REMOVED;
      } else {
            g_warning ("Unknown browse status\n");
            g_free (idle_data);
            return SW_OKAY;
      }
      
      idle_data->service.name = g_strdup (name);
      idle_data->service.type = g_strdup (type);
      idle_data->service.domain = g_strdup (domain);

      /* We don't want last dots in the domain or type */
      len = strlen (idle_data->service.type);
      if (len > 0 && idle_data->service.type[len-1] == '.')
            idle_data->service.type[len-1] = 0;
      len = strlen (idle_data->service.domain);
      if (len > 0 && idle_data->service.domain[len-1] == '.')
            idle_data->service.domain[len-1] = 0;
      
      g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
                   howl_browse_idle,
                   idle_data,
                   (GDestroyNotify) browse_idle_data_free);
      return SW_OKAY;
}

#endif /* HAVE_HOWL */

/**
 * gnome_vfs_dns_sd_browse:
 * @handle: pointer to a pointer to a #GnomeVFSDNSSDBrowseHandle object.
 * @domain: dns domain to browse, or "local" for multicast DNS.
 * @type: type of service to browse for.
 * @callback: function to be called when service is discovered.
 * @callback_data: data to pass to @callback.
 * @callback_data_destroy_func: optional destructor function for @callback_data.
 *
 * Browses @domain for service of the type @type, calling @callback whenever
 * a new one is found or removed.
 *
 * The domain to use can be "local" for multicast dns on the local network
 * (known as mDNS), or it can be the domain of the current host. You can also
 * use gnome_vfs_dns_sd_list_browse_domains_sync() to get a list of domains
 * that are interested in a particular domain.
 *
 * The type is a string of the form "_type._tcp" or "_type._udp", where type
 * is a service type registered at http://www.dns-sd.org/ServiceTypes.html.
 *
 * Return value: an integer representing the result of the operation.
 */
GnomeVFSResult
gnome_vfs_dns_sd_browse (GnomeVFSDNSSDBrowseHandle **handle_out,
                   const char *domain,
                   const char *type,
                   GnomeVFSDNSSDBrowseCallback callback,
                   gpointer callback_data,
                   GDestroyNotify callback_data_destroy_func)
{
      GnomeVFSDNSSDBrowseHandle *handle;

      *handle_out = NULL;
      
      handle = g_new0 (GnomeVFSDNSSDBrowseHandle, 1);
      handle->domain = g_strdup (domain);
      handle->type = g_strdup (type);
      handle->callback = callback;
      handle->callback_data = callback_data;
      handle->callback_data_destroy_func = callback_data_destroy_func;
      
      if (strcmp (domain, "local") == 0) {
#ifdef HAVE_AVAHI
            AvahiClient *client;
            AvahiServiceBrowser *sb;

            handle->is_local = TRUE;
            client = get_global_avahi_client ();
            if (client) {
                  sb = avahi_service_browser_new (client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, type, NULL, 
                                          AVAHI_LOOKUP_USE_MULTICAST,
                                          avahi_browse_callback, handle);
                  if (sb != NULL) {
                        handle->avahi_sb = sb;
                        *handle_out = handle;
                        return GNOME_VFS_OK;
                  }
                  g_warning ("Failed to create service browser: %s\n", avahi_strerror( avahi_client_errno (client)));
            }
            return GNOME_VFS_ERROR_GENERIC;
#elif defined (HAVE_HOWL)
            sw_result res;
            sw_discovery session;
            
            handle->is_local = TRUE;

            session = get_global_howl_session ();
            if (session) {
                  res = sw_discovery_browse (session,
                                       0, 
                                       type, domain,
                                       howl_browse_reply,
                                       handle,
                                       &handle->howl_id);
                  
                  if (res == SW_OKAY) {
                        *handle_out = handle;
                        return GNOME_VFS_OK;
                  }
            }
            return GNOME_VFS_ERROR_GENERIC;
#else
            free_browse_handle (handle);
            return GNOME_VFS_ERROR_NOT_SUPPORTED;
#endif
      } else {
            handle->is_local = FALSE;
            if (g_thread_create (unicast_browse_thread, handle,
                             FALSE, NULL) == NULL) {
                  g_free (handle->domain);
                  g_free (handle->type);
                  g_free (handle);
                  return GNOME_VFS_ERROR_INTERNAL;
            }
            *handle_out = handle;
            return GNOME_VFS_OK;
      }
}

/**
 * gnome_vfs_dns_sd_stop_browse:
 * @handle: handle of the browse operation to be stopped.
 *
 * Stops browsing a domain started with gnome_vfs_dns_sd_browse().
 *
 * Return value: an integer representing the result of the operation.
 */
GnomeVFSResult
gnome_vfs_dns_sd_stop_browse (GnomeVFSDNSSDBrowseHandle *handle)
{
      if (handle->is_local) {
#ifdef HAVE_AVAHI
            handle->cancelled = TRUE;
            avahi_service_browser_free (handle->avahi_sb);
            free_browse_handle (handle);
#endif
#ifdef HAVE_HOWL
            handle->cancelled = TRUE;
            sw_discovery_cancel (get_global_howl_session (), handle->howl_id);
#endif
            return GNOME_VFS_OK;
      } else {
            if (handle->finished)
                  free_browse_handle (handle);
            else
                  handle->cancelled = TRUE;
            return GNOME_VFS_OK;
      }
}

struct GnomeVFSDNSSDResolveHandle {
      char *name;
      char *domain;
      char *type;
      GnomeVFSDNSSDResolveCallback callback;
      gpointer callback_data;
      GDestroyNotify callback_data_destroy_func;

      gboolean is_local;

      char *host;
      int port;
      char *text;
      int text_len;
      
      /* multicast: */
#ifdef HAVE_AVAHI
      AvahiServiceResolver *avahi_sr;
#endif

#ifdef HAVE_HOWL
      sw_discovery_oid howl_id;
      guint timeout_tag;
#endif
      
      /* unicast data: */
      gboolean cancelled;
      GnomeVFSResult res;
      guint idle_tag;
};


static void
free_resolve_handle (GnomeVFSDNSSDResolveHandle *handle)
{
      g_free (handle->name);
      g_free (handle->domain);
      g_free (handle->type);
      
      g_free (handle->host);
      g_free (handle->text);

      if (handle->callback_data_destroy_func != NULL)
            handle->callback_data_destroy_func (handle->callback_data);
      
      g_free (handle);
}

static gboolean
unicast_resolve_idle (gpointer data)
{
      GnomeVFSDNSSDResolveHandle *handle;
      GnomeVFSDNSSDService service;
      GHashTable *hash;

      handle = data;
      
      if (!handle->cancelled) {
            service.name = handle->name;
            service.type = handle->type;
            service.domain = handle->domain;

            hash = decode_txt_record (handle->text,
                                handle->text_len);
            
            handle->callback (data,
                          handle->res,
                          &service,
                          handle->host,
                          handle->port,
                          hash,
                          handle->text_len,
                          handle->text,
                          handle->callback_data);

            if (hash)
                  g_hash_table_destroy (hash);
      }

      free_resolve_handle (handle);
      
      return FALSE;
}

static gpointer
unicast_resolve_thread (gpointer data)
{
      GnomeVFSDNSSDResolveHandle *handle;
      handle = data;

      handle->res = unicast_resolve_sync (handle->name,
                                  handle->type,
                                  handle->domain,
                                  &handle->host, &handle->port,
                                  &handle->text_len, &handle->text);
      g_idle_add (unicast_resolve_idle,
                handle);
      
      return NULL;
}

#ifdef HAVE_AVAHI
static void
avahi_resolve_async_callback (AvahiServiceResolver *r,
                        AvahiIfIndex interface,
                        AvahiProtocol protocol,
                        AvahiResolverEvent event,
                        const char *name,
                        const char *type,
                        const char *domain,
                        const char *host_name,
                        const AvahiAddress *address,
                        uint16_t port,
                        AvahiStringList *txt,
                        AvahiLookupResultFlags flags,
                        void *user_data)
{
      GnomeVFSDNSSDResolveHandle *handle;
      GnomeVFSDNSSDService service;
      GHashTable *hash;
      size_t text_len;
      char *text;
      char host[128];

      handle = user_data;
      if (event == AVAHI_RESOLVER_FOUND) {
            text_len = avahi_string_list_serialize (txt, NULL, 0);
            text = g_malloc (text_len);
            text_len = avahi_string_list_serialize (txt, text, text_len);

            hash = decode_txt_record (text, text_len);

            service.name = (char *)name;
            service.type = (char *)type;
            service.domain = (char *)domain;
            
            avahi_address_snprint (host, sizeof(host), address);
            handle->callback (handle,
                          GNOME_VFS_OK,
                          &service,
                          host,
                          port,
                          hash,
                          handle->text_len,
                          handle->text,
                          handle->callback_data);
            if (hash) {
                  g_hash_table_destroy (hash);
            }
            g_free (text);

      } else if (event == AVAHI_RESOLVER_FAILURE) {
            handle->callback (handle,
                          GNOME_VFS_ERROR_HOST_NOT_FOUND,
                          NULL,
                          NULL, 0,
                          NULL, 0, NULL,
                          handle->callback_data);
      }
      
      avahi_service_resolver_free (r);
      free_resolve_handle (handle);
}

#endif


#ifdef HAVE_HOWL
static gboolean
howl_resolve_idle (gpointer data)
{
      GnomeVFSDNSSDResolveHandle *handle;
      GnomeVFSDNSSDService service;
      GHashTable *hash;

      handle = data;

      hash = decode_txt_record (handle->text,
                          handle->text_len);

      service.name = handle->name;
      service.type = handle->type;
      service.domain = handle->domain;

      handle->callback (handle,
                    GNOME_VFS_OK,
                    &service,
                    handle->host,
                    handle->port,
                    hash,
                    handle->text_len,
                    handle->text,
                    handle->callback_data);

      if (hash) {
            g_hash_table_destroy (hash);
      }

      free_resolve_handle (handle);
      
      return FALSE;
}



static sw_result
howl_resolve_reply (sw_discovery                   discovery,
                sw_discovery_oid               id,
                sw_uint32                    interface_index,
                sw_const_string                name,
                sw_const_string                type,
                sw_const_string                domain,
                sw_ipv4_address                address,
                sw_port                        port,
                sw_octets                      text_record,
                sw_ulong                       text_record_len,
                sw_opaque                      extra)
{
      GnomeVFSDNSSDResolveHandle *handle;

      handle = extra;

      g_assert (handle->idle_tag == 0);

      handle->host = g_malloc (16);
      sw_ipv4_address_name (address, handle->host, 16);
      handle->port = port;
      handle->text = g_memdup (text_record, text_record_len);
      handle->text_len = text_record_len;

      /* We want no more replies */
      sw_discovery_cancel (get_global_howl_session (),
                       handle->howl_id);
      g_source_remove (handle->timeout_tag);
      
      handle->idle_tag = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
                                  howl_resolve_idle,
                                  handle,
                                  (GDestroyNotify) NULL);
      return SW_OKAY;
}

static gboolean
howl_resolve_timeout (gpointer data)
{
      GnomeVFSDNSSDResolveHandle *handle;
      
      handle = data;

      handle->callback (handle,
                    GNOME_VFS_ERROR_HOST_NOT_FOUND,
                    NULL,
                    NULL, 0,
                    NULL, 0, NULL,
                    handle->callback_data);

      if (handle->idle_tag) {
            /* We already resolved, but the idle hasn't run yet */
            g_source_remove (handle->idle_tag);
      } else {
            /* TODO: We shouldn't get any callbacks after stopping,
               but there is a bug in howl 0.9.5 where it can still
               happen */
            sw_discovery_cancel (get_global_howl_session (),
                             handle->howl_id);
      }
      
      free_resolve_handle (handle);
      
      return FALSE;
}

#endif

/**
 * gnome_vfs_dns_sd_resolve:
 * @handle: pointer to a pointer to a #GnomeVFSDNSSDResolveHandle object.
 * @name: name of the service to resolve in UTF-8 encoding.
 * @type: type of the service to resolve.
 * @domain: dns domain of the service to resolve, or "local" for multicast DNS.
 * @timeout: maximum time (in milliseconds) to try to resolve, or zero if no maximum.
 * @callback: function to be called when the service has been resolved.
 * @callback_data: data to pass to @callback.
 * @callback_data_destroy_func: optional destructor function for @callback_data.
 *
 * Tries to resolve a specific service (typically recieved from
 * gnome_vfs_dns_sd_browse()) into a hostname/ip, port number and additional
 * options.
 *
 * If you ever have to save a reference to a service you should store the
 * unresolved name/type/domain tripplet, because the actual host for the
 * service can change.
 *
 * The @timeout argument is primarily useful for local resolves, since the
 * host owning the service might no longer be around to answer.
 * 
 * Return value: an integer representing the result of the operation.
 */
GnomeVFSResult                      
gnome_vfs_dns_sd_resolve (GnomeVFSDNSSDResolveHandle **handle_out,
                    const char *name,
                    const char *type,
                    const char *domain,
                    int timeout,
                    GnomeVFSDNSSDResolveCallback callback,
                    gpointer callback_data,
                    GDestroyNotify callback_data_destroy_func)
{
      GnomeVFSDNSSDResolveHandle *handle;

      *handle_out = NULL;
      
      handle = g_new0 (GnomeVFSDNSSDResolveHandle, 1);
      handle->name = g_strdup (name);
      handle->domain = g_strdup (domain);
      handle->type = g_strdup (type);
      handle->callback = callback;
      handle->callback_data = callback_data;
      handle->callback_data_destroy_func = callback_data_destroy_func;
      
      if (strcmp (domain, "local") == 0) {
#ifdef HAVE_AVAHI
            AvahiClient *client;
            AvahiServiceResolver *sr;

            handle->is_local = TRUE;
            client = get_global_avahi_client ();
            if (client) {
                  sr = avahi_service_resolver_new (client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 
                                           name, type, domain, AVAHI_PROTO_UNSPEC, 
                                           0, avahi_resolve_async_callback, handle);
                  if (sr != NULL) {
                        handle->avahi_sr = sr;
                        *handle_out = handle;
                        return GNOME_VFS_OK;
                  }
            }
            return GNOME_VFS_ERROR_GENERIC;
#elif defined (HAVE_HOWL)
            sw_result res;
            sw_discovery session;
            
            handle->is_local = TRUE;

            session = get_global_howl_session ();
            if (session) {
                  res = sw_discovery_resolve (session,
                                        0, 
                                        name,
                                        type,
                                        domain,
                                        howl_resolve_reply,
                                        handle,
                                        &handle->howl_id);
                  if (res == SW_OKAY) {
                        if (timeout != 0) {
                              handle->timeout_tag = g_timeout_add (timeout,
                                                           howl_resolve_timeout,
                                                           handle);
                        }
                        
                        *handle_out = handle;
                        return GNOME_VFS_OK;
                  }
            }
            return GNOME_VFS_ERROR_GENERIC;
#else
            return GNOME_VFS_ERROR_NOT_SUPPORTED;
#endif
      } else {
            handle->is_local = FALSE;
            if (g_thread_create (unicast_resolve_thread, handle,
                             FALSE, NULL) == NULL) {
                  g_free (handle->domain);
                  g_free (handle->type);
                  g_free (handle);
                  return GNOME_VFS_ERROR_INTERNAL;
            }
            *handle_out = handle;
            return GNOME_VFS_OK;
      }
}

/**
 * gnome_vfs_dns_sd_cancel_resolve:
 * @handle: handle of the resolve operation to be cancelled.
 *
 * Cancels resolving a service started with gnome_vfs_dns_sd_resolve().
 *
 * Return value: an integer representing the result of the operation.
 */
GnomeVFSResult
gnome_vfs_dns_sd_cancel_resolve (GnomeVFSDNSSDResolveHandle *handle)
{
      if (handle->is_local) {
#ifdef HAVE_AVAHI
            avahi_service_resolver_free (handle->avahi_sr);
            free_resolve_handle (handle);
#endif
#ifdef HAVE_HOWL
            g_source_remove (handle->timeout_tag);
            if (handle->idle_tag) {
                  /* We already resolved, but the idle hasn't run yet */
                  g_source_remove (handle->idle_tag);
            } else {
                  /* TODO: We shouldn't get any callbacks after stopping,
                     but there is a bug in howl 0.9.5 where it can still
                     happen */
                  sw_discovery_cancel (get_global_howl_session (),
                                   handle->howl_id);
            }
            free_resolve_handle (handle);
            
#endif
            return GNOME_VFS_OK;
      } else {
            handle->cancelled = TRUE;
            return GNOME_VFS_OK;
      }
}

#if defined(HAVE_AVAHI) || defined(HAVE_HOWL)
static int
find_existing_service (GArray *array,
                   const char *name,
                   const char *type,
                   const char *domain)
{
      GnomeVFSDNSSDService *existing;
      int i;
      
      for (i = 0; i < array->len; i++) {
            existing = &g_array_index (array, GnomeVFSDNSSDService, i);
            if (strcmp (existing->name, name) == 0 &&
                strcmp (existing->type, type) == 0 &&
                strcmp (existing->domain, domain) == 0) {
                  return i;
            }
      }
      return -1;
                
}
#endif


#ifdef HAVE_AVAHI
struct sync_browse_data {
      AvahiSimplePoll *poll;
      GArray *array;
};

static void
avahi_browse_sync_client_callback (AvahiClient *client, AvahiClientState state, void *user_data)
{
      struct sync_browse_data *data;

      data = user_data;
      if (state == AVAHI_CLIENT_FAILURE) {
            avahi_simple_poll_quit (data->poll);
      }
}

static void 
avahi_browse_sync_callback (AvahiServiceBrowser *b,
                      AvahiIfIndex interface,
                      AvahiProtocol protocol,
                      AvahiBrowserEvent event,
                      const char *name,
                      const char *type,
                      const char *domain,
                      AvahiLookupResultFlags flags,
                      void *user_data)
{
      struct sync_browse_data *data;
      GnomeVFSDNSSDService service, *existing;
      int i;
      gboolean free_service;

      data = user_data;
      
      free_service = TRUE;
      service.name = g_strdup (name);
      service.type = g_strdup (type);
      service.domain = g_strdup (domain);
      
      if (event == AVAHI_BROWSER_NEW) {
            if (find_existing_service (data->array, service.name, service.type,
                                 service.domain) == -1) {
                  free_service = FALSE;
                  g_array_append_val (data->array, service);
            } 
      } else if (event == AVAHI_BROWSER_REMOVE) {
            i = find_existing_service (data->array, service.name, service.type,
                                 service.domain);
            if (i != -1) {
                  existing = &g_array_index (data->array, GnomeVFSDNSSDService, i);
                  g_free (existing->name);
                  g_free (existing->type);
                  g_free (existing->domain);
                  g_array_remove_index (data->array, i);
            }
      } else if (event == AVAHI_BROWSER_ALL_FOR_NOW) {
            avahi_simple_poll_quit (data->poll);
      }


      if (free_service) {
            g_free (service.name);
            g_free (service.type);
            g_free (service.domain);
      }     
}

static void
stop_poll_timeout (AvahiTimeout *timeout, void *user_data)
{
      AvahiSimplePoll *poll = user_data;
      
      avahi_simple_poll_quit (poll);
}

#endif


#ifdef HAVE_HOWL


static sw_result
howl_browse_reply_sync (sw_discovery                  discovery,
                  sw_discovery_oid              id,
                  sw_discovery_browse_status    status,
                  sw_uint32               interface_index,
                  sw_const_string               name,
                  sw_const_string               type,
                  sw_const_string               domain,
                  sw_opaque                     extra)
{
      GnomeVFSDNSSDService service, *existing;
      GArray *array;
      int i, len;
      gboolean free_service;

      array = extra;
      
      if (status == SW_DISCOVERY_BROWSE_RELEASE) {
            /* free in an idle to make sure the other idles are done,
               and to give sane environment for destroy callback */
            return SW_OKAY;
      }

      free_service = TRUE;
      service.name = g_strdup (name);
      service.type = g_strdup (type);
      service.domain = g_strdup (domain);
      
      /* We don't want last dots in the domain or type */
      len = strlen (service.type);
      if (len > 0 && service.type[len-1] == '.')
            service.type[len-1] = 0;
      len = strlen (service.domain);
      if (len > 0 && service.domain[len-1] == '.')
            service.domain[len-1] = 0;
      
      if (status == SW_DISCOVERY_BROWSE_ADD_SERVICE) {
            if (find_existing_service (array, service.name, service.type,
                                 service.domain) == -1) {
                  free_service = FALSE;
                  g_array_append_val (array, service);
            } 
      } else if (status == SW_DISCOVERY_BROWSE_REMOVE_SERVICE) {
            i = find_existing_service (array, service.name, service.type,
                                 service.domain);
            if (i != -1) {
                  existing = &g_array_index (array, GnomeVFSDNSSDService, i);
                  g_free (existing->name);
                  g_free (existing->type);
                  g_free (existing->domain);
                  g_array_remove_index (array, i);
            }
      } else {
            g_warning ("Unknown browse status\n");
      }
      
      if (free_service) {
            g_free (service.name);
            g_free (service.type);
            g_free (service.domain);
      }     
      return SW_OKAY;
}
#endif /* HAVE_HOWL */

/**
 * gnome_vfs_dns_sd_browse_sync:
 * @domain: The dns domain to browse, or "local" for multicast DNS.
 * @type: type of the service to browse for.
 * @timeout_msec: maximum time to browse, in milliseconds.
 * @n_services: pointer to location to store number of returned services.
 * @services: pointer to location to store returned services.
 *
 * Browses @domain for service of the type @type, returning the result
 * after blocking for the duration of the browse. For details about @domain
 * and @type, see gnome_vfs_dns_sd_browse().
 *
 * @timeout is essential for the "local" domain, since you can never really
 * know when you've gotten the full set of return values when using multicast.
 *
 * The returned list can be freed with gnome_vfs_dns_sd_service_list_free().
 * 
 * This is a synchronous version of gnome_vfs_dns_sd_browse(), see that for
 * more details.
 *
 * Return value: an integer representing the result of the operation.
 */
GnomeVFSResult
gnome_vfs_dns_sd_browse_sync (const char *domain,
                        const char *type,
                        int timeout_msec,
                        int *n_services,
                        GnomeVFSDNSSDService **services)
{
      *n_services = 0;
      *services = NULL;
      
      if (strcmp (domain, "local") == 0) {
#ifdef HAVE_AVAHI
            AvahiSimplePoll *simple_poll;
            const AvahiPoll *poll;
            AvahiClient *client = NULL;
            AvahiServiceBrowser *sb;
            int error;
            GArray *array;
            struct sync_browse_data data;
            struct timeval tv;

            simple_poll = avahi_simple_poll_new ();
            data.poll = simple_poll;
            if (simple_poll == NULL) {
                  g_warning ("Failed to create simple poll object");
                  return GNOME_VFS_ERROR_GENERIC;
            }

            poll = avahi_simple_poll_get (simple_poll);
            client = avahi_client_new (poll, 0,
                                 avahi_browse_sync_client_callback, &data, &error);
            
            /* Check wether creating the client object succeeded */
            if (client == NULL) {
                  g_warning ("Failed to create client: %s\n", avahi_strerror (error));
                  avahi_simple_poll_free (simple_poll);
                  return GNOME_VFS_ERROR_GENERIC;
            }


            array = g_array_new (FALSE, FALSE, sizeof (GnomeVFSDNSSDService));
            data.array = array;
            sb = avahi_service_browser_new (client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, type, NULL, 
                                    AVAHI_LOOKUP_USE_MULTICAST, avahi_browse_sync_callback, &data);
            if (sb == NULL) {
                  g_warning ("Failed to create service browser: %s\n", avahi_strerror (avahi_client_errno (client)));
                  g_array_free (array, TRUE);
                  avahi_client_free (client);
                  avahi_simple_poll_free (simple_poll);
                  return GNOME_VFS_ERROR_GENERIC;
            }


            avahi_elapse_time (&tv, timeout_msec,  0);
            poll->timeout_new (poll, &tv, stop_poll_timeout, (void *)simple_poll);

            /* Run the main loop util reply or timeout */
            for (;;)
                  if (avahi_simple_poll_iterate (simple_poll, -1) != 0)
                        break;

            
            avahi_service_browser_free (sb);
            avahi_client_free (client);
            avahi_simple_poll_free (simple_poll);

            *n_services = array->len;
            *services = (GnomeVFSDNSSDService *)g_array_free (array, FALSE);
            
            return GNOME_VFS_OK;
#elif defined (HAVE_HOWL)
            sw_discovery session;
            sw_salt salt;
            sw_result res;
            sw_ulong timeout;
            sw_discovery_oid browse_id;
            struct timeval end_tv, tv;
            GArray *array;

            if (sw_discovery_init (&session) != SW_OKAY) {
                  g_warning ("gnome_vfs_dns_sd_browse_sync - howl init failed\n");
                  return GNOME_VFS_ERROR_GENERIC;
            }

            if (sw_discovery_salt (session, &salt) != SW_OKAY) {
                  g_warning ("gnome_vfs_dns_sd_browse_sync - couldn't get salt\n");
                  sw_discovery_fina (session);
                  return GNOME_VFS_ERROR_GENERIC;
            }
            
            array = g_array_new (FALSE, FALSE, sizeof (GnomeVFSDNSSDService));
            res = sw_discovery_browse (session,
                                 0,
                                 type, domain,
                                 howl_browse_reply_sync,
                                 array,
                                 &browse_id);
            if (res != SW_OKAY) {
                  g_warning ("gnome_vfs_dns_sd_browse_sync - howl browse failed\n");
                  g_array_free (array, TRUE);
                  sw_discovery_fina (session);
                  return GNOME_VFS_ERROR_GENERIC;
            }
            
            gettimeofday (&end_tv, NULL);
            tv = end_tv;
            
            end_tv.tv_sec += timeout_msec / 1000;
            end_tv.tv_usec += (timeout_msec % 1000) * 1000;
            end_tv.tv_sec += end_tv.tv_usec / 1000000;
            end_tv.tv_usec %= 1000000;
            
            do {
                  timeout = timeout_msec;
                  sw_salt_step (salt, &timeout);

                  gettimeofday (&tv, NULL);
                  timeout_msec = (end_tv.tv_sec - tv.tv_sec) * 1000 + 
                        (end_tv.tv_usec - tv.tv_usec) / 1000;
            } while (timeout_msec > 0);
            
            sw_discovery_cancel (session, browse_id);
                                
            sw_discovery_fina (session);

            *n_services = array->len;
            *services = (GnomeVFSDNSSDService *)g_array_free (array, FALSE);
            
            return GNOME_VFS_OK;
#else
            return GNOME_VFS_ERROR_NOT_SUPPORTED;
#endif
      } else {
            return unicast_browse_sync (domain, type,
                                  n_services,
                                  services);
      }
}

#ifdef HAVE_AVAHI
struct sync_resolve_data {
      AvahiSimplePoll *poll;
      gboolean got_data;
      char *host;
      int port;
      char *text;
      int text_len;
};


static void
avahi_resolve_sync_client_callback (AvahiClient *c, AvahiClientState state, void *user_data)
{
      struct sync_resolve_data *data;

      data = user_data;
      if (state == AVAHI_CLIENT_FAILURE) {
            avahi_simple_poll_quit (data->poll);
      }
}

static void
avahi_resolve_sync_callback (AvahiServiceResolver *r,
                       AvahiIfIndex interface,
                       AvahiProtocol protocol,
                       AvahiResolverEvent event,
                       const char *name,
                       const char *type,
                       const char *domain,
                       const char *host_name,
                       const AvahiAddress *address,
                       uint16_t port,
                       AvahiStringList *txt,
                       AvahiLookupResultFlags flags,
                       void *user_data)
{
      struct sync_resolve_data *data;
      char a[128];

      data = user_data;
      if (event == AVAHI_RESOLVER_FOUND) {
            data->got_data = TRUE;
            avahi_address_snprint (a, sizeof(a), address);
            data->host = g_strdup (a);
            data->port = port;
            data->text_len = avahi_string_list_serialize (txt, NULL, 0);
            data->text = g_malloc (data->text_len);
            avahi_string_list_serialize (txt, data->text, data->text_len);
      }
      
      avahi_service_resolver_free (r);
        avahi_simple_poll_quit (data->poll);
}

#endif

#ifdef HAVE_HOWL
struct sync_resolve_data {
      gboolean got_data;
      char *host;
      int port;
      char *text;
      int text_len;
};

static sw_result
howl_resolve_reply_sync (sw_discovery                   discovery,
                   sw_discovery_oid               id,
                   sw_uint32                  interface_index,
                   sw_const_string                name,
                   sw_const_string                type,
                   sw_const_string                domain,
                   sw_ipv4_address                address,
                   sw_port                        port,
                   sw_octets                      text_record,
                   sw_ulong                       text_record_len,
                   sw_opaque                      extra)
{
      struct sync_resolve_data *data;

      data = extra;
      data->got_data = TRUE;
      data->host = g_malloc (16);
      sw_ipv4_address_name (address, data->host, 16);
      data->port = port;
      data->text = g_memdup (text_record, text_record_len);
      data->text_len = text_record_len;
      
      return SW_OKAY;
}
#endif

/**
 * gnome_vfs_dns_sd_resolve_sync:
 * @name: name of the service to resolve in UTF-8 encoding.
 * @type: type of the service to resolve.
 * @domain: dns domain of the service to resolve, or "local" for multicast DNS.
 * @timeout_msec: maximum time(in milliseconds) to try to resolve.
 * @host: location to store the host name or ip of the host hosting the service.
 * @port: location to store the port number to use for the service.
 * @text: location to store a hash table giving additional options about the service.
 * @text_raw_len_out: location to store length of @text_raw_out.
 * @text_raw_out: location to store raw version of the additional options in @text.
 *
 * Tries to resolve a specific service (typically recieved from
 * gnome_vfs_dns_sd_browse()) into a hostname/ip, port number and additional
 * options.
 *
 * This is a synchronous version of gnome_vfs_dns_sd_resolve(), see that (and
 * its callback GnomeVFSDNSSDResolveCallback()) for more details.
 *
 * Return value: an integer representing the result of the operation.
 */
GnomeVFSResult                      
gnome_vfs_dns_sd_resolve_sync (const char *name,
                         const char *type,
                         const char *domain,
                         int timeout_msec,
                         char **host,
                         int *port,
                         GHashTable **text,
                         int *text_raw_len_out,
                         char **text_raw_out)
{
      int text_raw_len;
      char *text_raw;
      GnomeVFSResult res;
      
      if (strcmp (domain, "local") == 0) {
#ifdef HAVE_AVAHI
            AvahiSimplePoll *simple_poll;
            AvahiClient *client = NULL;
            AvahiServiceResolver *sr;
            int error;
            struct sync_resolve_data resolve_data = {0};

            simple_poll = avahi_simple_poll_new ();
            resolve_data.poll = simple_poll;
            if (simple_poll == NULL) {
                  g_warning ("Failed to create simple poll object");
                  return GNOME_VFS_ERROR_GENERIC;
            }

            client = avahi_client_new (avahi_simple_poll_get (simple_poll), 0, 
                                 avahi_resolve_sync_client_callback, &resolve_data, &error);
            
            /* Check wether creating the client object succeeded */
            if (client == NULL) {
                  g_warning ("Failed to create client: %s\n", avahi_strerror (error));
                  avahi_simple_poll_free (simple_poll);
                  return GNOME_VFS_ERROR_GENERIC;
            }
            
            sr = avahi_service_resolver_new (client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 
                                     name, type, domain, AVAHI_PROTO_UNSPEC, 
                                     0, avahi_resolve_sync_callback, &resolve_data);
            if (sr == NULL) {
                  g_warning ("Failed to resolve service '%s': %s\n", name, avahi_strerror (avahi_client_errno (client)));
                  avahi_client_free (client);
                  avahi_simple_poll_free (simple_poll);
                  return GNOME_VFS_ERROR_GENERIC;
            }

            /* Run the main loop util reply or timeout */
            for (;;)
                  if (avahi_simple_poll_iterate (simple_poll, -1) != 0)
                        break;

            avahi_client_free (client);
            avahi_simple_poll_free (simple_poll);

            if (resolve_data.got_data) {
                  *host = resolve_data.host;
                  *port = resolve_data.port;
                  if (text != NULL)
                        *text = decode_txt_record (resolve_data.text, resolve_data.text_len);
                  if (text_raw_len_out != NULL && text_raw_out) {
                        *text_raw_len_out = resolve_data.text_len;
                        *text_raw_out = resolve_data.text;
                  } else {
                        g_free (resolve_data.text);
                  }
                  return GNOME_VFS_OK;
            }
            
            return GNOME_VFS_ERROR_HOST_NOT_FOUND;
#elif defined (HAVE_HOWL)
            sw_discovery session;
            sw_salt salt;
            sw_result res;
            sw_ulong timeout;
            sw_discovery_oid resolve_id;
            struct timeval end_tv, tv;
            struct sync_resolve_data resolve_data = {0};
            
            if (sw_discovery_init (&session) != SW_OKAY) {
                  g_warning ("gnome_vfs_dns_sd_resolve_sync - howl init failed\n");
                  return GNOME_VFS_ERROR_GENERIC;
            }

            if (sw_discovery_salt (session, &salt) != SW_OKAY) {
                  g_warning ("gnome_vfs_dns_sd_resolve_sync - couldn't get salt\n");
                  sw_discovery_fina (session);
                  return GNOME_VFS_ERROR_GENERIC;
            }
            
            res = sw_discovery_resolve (session,
                                  0, 
                                  name, type, domain,
                                  howl_resolve_reply_sync,
                                  &resolve_data,
                                  &resolve_id);
            if (res != SW_OKAY) {
                  g_warning ("gnome_vfs_dns_sd_resolve_sync - howl resolve failed\n");
                  sw_discovery_fina (session);
                  return GNOME_VFS_ERROR_GENERIC;
            }
            
            gettimeofday (&end_tv, NULL);
            tv = end_tv;
            
            end_tv.tv_sec += timeout_msec / 1000;
            end_tv.tv_usec += (timeout_msec % 1000) * 1000;
            end_tv.tv_sec += end_tv.tv_usec / 1000000;
            end_tv.tv_usec %= 1000000;
            
            do {
                  timeout = timeout_msec;
                  sw_salt_step (salt, &timeout);

                  gettimeofday (&tv, NULL);
                  timeout_msec = (end_tv.tv_sec - tv.tv_sec) * 1000 + 
                        (end_tv.tv_usec - tv.tv_usec) / 1000;
            } while (!resolve_data.got_data && timeout_msec > 0);
            
            sw_discovery_cancel (session, resolve_id);
                                
            sw_discovery_fina (session);

            if (resolve_data.got_data) {
                  *host = resolve_data.host;
                  *port = resolve_data.port;
                  if (text != NULL)
                        *text = decode_txt_record (resolve_data.text, resolve_data.text_len);
                  if (text_raw_len_out != NULL && text_raw_out) {
                        *text_raw_len_out = resolve_data.text_len;
                        *text_raw_out = resolve_data.text;
                  } else {
                        g_free (resolve_data.text);
                  }
                  return GNOME_VFS_OK;
            }
            
            return GNOME_VFS_ERROR_HOST_NOT_FOUND;
#else
            return GNOME_VFS_ERROR_NOT_SUPPORTED;
#endif
      } else {
            res = unicast_resolve_sync (name, type, domain,
                                  host, port,
                                  &text_raw_len, &text_raw);

            if (res == GNOME_VFS_OK) {
                  if (text != NULL) {
                        *text = decode_txt_record (text_raw, text_raw_len);
                  }
                  
                  if (text_raw_len_out != NULL) {
                        *text_raw_len_out = text_raw_len;
                        *text_raw_out = text_raw;
                  } else {
                        g_free (text_raw);
                  }
            }

            return res;
      }
}

/**
 * gnome_vfs_dns_sd_service_list_free:
 * @services: the list of services to free.
 * @n_services: the number of services to free.
 *
 * Frees a list of services as returned by gnome_vfs_dns_sd_browse_sync().
 */
void
gnome_vfs_dns_sd_service_list_free (GnomeVFSDNSSDService *services,
                            int n_services)
{
      int i;
      
      for (i = 0; i < n_services; i++) {
            g_free (services[i].name);
            g_free (services[i].type);
            g_free (services[i].domain);
      }
      g_free (services);
}


/**
 * gnome_vfs_dns_sd_list_browse_domains_sync:
 * @domain: the domain to list browsable domains in.
 * @timeout_msec: maximum time to run, in milliseconds.
 * @domains: location to store the returned list of domain names strings.
 *
 * Lists the recommended browsing domains for a specific dns domain.
 * This can be used to find interesting domains for the domain
 * you are currently in. These can then be browsed with gnome_vfs_dns_sd_browse().
 *
 * Return value: an integer representing the result of the operation.
 */
GnomeVFSResult
gnome_vfs_dns_sd_list_browse_domains_sync (const char *domain,
                                 int timeout_msec,
                                 GList **domains)
{
      if (strcmp (domain, "local") == 0) {
            /* TODO: Not supported at the moment */
            return GNOME_VFS_ERROR_NOT_SUPPORTED;
      } else {
            return unicast_list_domains_sync (domain, domains);
      }
}

/**
 * gnome_vfs_get_default_browse_domains:
 *
 * Returns a list of domain names that is useful to
 * browse for standard services. The list is generated
 * by contacting the dns server of the domain part the
 * hostname and asking for the list of browse domains.
 * Then extra domains from a gconf setting is added.
 *
 * The "local" domain is not normally returned by this.
 * Care should be taken with local services so that its
 * obvious that they are local, and cannot be confused
 * with non-local services.
 *
 * Return value: a #GList of domain name strings.
 */
GList *
gnome_vfs_get_default_browse_domains (void)
{
      char hostname[256];
      char *domain, *dot;
      GList *domains;
      char *extra_domains;
      char **domainsv;
      GConfClient *client;
      int i;
      
      domain = NULL;
      if (gethostname (hostname, sizeof(hostname)) == 0) {
            dot = strchr (hostname, '.');
            if (dot != NULL &&
                dot[0] != 0 &&
                dot[1] != 0) {
                  domain = dot + 1;
            }
      }

      domains = NULL;
      if (domain != NULL) {
            gnome_vfs_dns_sd_list_browse_domains_sync (domain,
                                             2000,
                                             &domains);
            
      }

      if (!gconf_is_initialized ()) {
            if (!gconf_init (0, NULL, NULL)) {
                  return domains;
            }
      }

      client = gconf_client_get_default ();
      extra_domains = gconf_client_get_string (client, PATH_GCONF_GNOME_VFS_DNS_SD_EXTRA_DOMAINS, NULL);


      if (extra_domains != NULL) {
            domainsv = g_strsplit (extra_domains, ",", 0);
            
            for (i = 0; domainsv[i] != NULL; i++) {
                  domains = g_list_prepend (domains, g_strdup (domainsv[i]));
            }
            
            g_strfreev (domainsv);
      }

      g_free (extra_domains);
      
      g_object_unref (G_OBJECT (client));

      return domains;
}

Generated by  Doxygen 1.6.0   Back to index