#include "gskdnslocalresolver.h"
#include "../gskerror.h"
#include "../gskmacros.h"

enum
{
  PROP_0,
  PROP_RR_CACHE
};

/* DB: Mar 4, 2005.  I don't know why this has been
   commented out, and i don't think it's needed by
   anyone either.  Investigate, maybe. */
#define IMPLEMENT_DNS_RESOLVER_IFACE    0

/*
 * A GskDnsResolver which never defers to the network,
 * or blocks. 
 */
static GObjectClass *parent_class = NULL;

struct _GskDnsLocalResolverClass
{
  GObjectClass		object_class;
};

struct _GskDnsLocalResolver
{
  GObject		object;
  GskDnsRRCache        *rr_cache;
};

static void
gsk_dns_local_resolver_finalize (GObject *object)
{
  GskDnsLocalResolver *resolver = GSK_DNS_LOCAL_RESOLVER (object);
  if (resolver->rr_cache != NULL)
    gsk_dns_rr_cache_unref (resolver->rr_cache);
  (*parent_class->finalize) (object);
}

#if IMPLEMENT_DNS_RESOLVER_IFACE
/* Note: this function should never be called... */
static void
gsk_dns_local_resolver_resolve_cancel  (GskDnsResolver            *resolver,
			                gpointer                   task)
{
  g_return_if_fail (GSK_IS_DNS_LOCAL_RESOLVER (resolver));
  g_return_if_fail (task != NULL);

  /* however, gsk_dns_local_resolver_resolve always returns NULL. */
  g_return_if_fail (task == NULL);
}
#endif

/**
 * gsk_dns_local_resolver_answer:
 * @rr_cache: resource-record cache to use to derive the answer to the question.
 * @question: question to answer.
 * @results: message to store results in.
 *
 * Attempt to compute an answer to a DNS question,
 * using only information locally available in
 * the resource-record cache.
 *
 * returns: the result of the query.
 */
GskDnsLocalResult
gsk_dns_local_resolver_answer        (GskDnsRRCache      *rr_cache,
				      GskDnsQuestion     *question,
				      GskDnsMessage      *results)
{
  const char *name = question->query_name;
  GSList *list;
  GSList *at;
  const char *cname = NULL;
  gboolean got_something = FALSE;

  g_return_val_if_fail (results != NULL, GSK_DNS_LOCAL_NO_DATA);
  if (rr_cache == NULL)
    {
      g_warning ("gsk_dns_local_resolver_resolve called without a cache");
      return GSK_DNS_LOCAL_NO_DATA;
    }

retry_lookup_name:
  list = gsk_dns_rr_cache_lookup_list (rr_cache,
				       name,
				       GSK_DNS_RR_WILDCARD,
				       question->query_class);
  if (list == NULL)
    {
      if (gsk_dns_rr_cache_is_negative (rr_cache, name,
					question->query_type,
					question->query_class))
	return GSK_DNS_LOCAL_NEGATIVE;

      for (;;)
	{
	  /* Ok, still go on a little hunt for additional
	     NS records up the hierarchy from name;
	     shovel those into the answer section. */
	  while (*name != '.' && *name != '\0')
	    name++;
	  while (*name == '.')
	    name++;
	  list = gsk_dns_rr_cache_lookup_list (rr_cache,
					       name,
					       GSK_DNS_RR_NAME_SERVER,
					       question->query_class);
	  if (list != NULL)
	    {
	      for (at = list; at != NULL; at = at->next)
		gsk_dns_rr_cache_lock (rr_cache, at->data);
	      results->answers = g_slist_concat (results->answers, list);
	      return GSK_DNS_LOCAL_PARTIAL_DATA;
	    }
	}
      return got_something ? GSK_DNS_LOCAL_PARTIAL_DATA : GSK_DNS_LOCAL_NO_DATA;
    }

  for (at = list; at != NULL; at = at->next)
    {
      GskDnsResourceRecord *record = at->data;
      if (record->type == question->query_type
       || record->type == GSK_DNS_RR_CANONICAL_NAME
       || question->query_type == GSK_DNS_RR_WILDCARD)
	{
	  gsk_dns_rr_cache_lock (rr_cache, record);
	  results->answers = g_slist_prepend (results->answers, record);
	  got_something = TRUE;
	}
      if (record->type == GSK_DNS_RR_CANONICAL_NAME)
	cname = record->rdata.domain_name;
    }

  /* and if we got a cname, and we weren't explicitly
     searching for CNAMEs (which by convention indicates you
     are not interested in CNAME's resolution),
     retry the whole query with name set to CNAME. */
  if (cname != NULL)
    {
      name = cname;
      cname = NULL;
      goto retry_lookup_name;
    }
  return got_something ? GSK_DNS_LOCAL_PARTIAL_DATA : GSK_DNS_LOCAL_NO_DATA;
}

#if IMPLEMENT_DNS_RESOLVER_IFACE
static gpointer
gsk_dns_local_resolver_real_resolve (GskDnsResolver               *resolver,
				     gboolean                      recursive,
				     GSList                       *questions,
				     GskDnsResolverResponseFunc    func,
				     GskDnsResolverFailFunc        on_fail,
				     gpointer                      func_data,
				     GDestroyNotify                destroy,
				     GskDnsResolverHints          *hints)
{
  GskDnsLocalResolver *local_resolver = GSK_DNS_LOCAL_RESOLVER (resolver);
  GskDnsMessage *allocator = gsk_dns_message_new (0, FALSE);
  GSList *negatives = NULL;
  gboolean got_something = FALSE;

  /* We always operate non-recursively. */
  (void) recursive;

  /* we don't do any ip-based filtering */
  (void) hints;

  while (questions != NULL)
    {
      GskDnsQuestion *question = (GskDnsQuestion*)(questions->data);
      switch (gsk_dns_local_resolver_answer (local_resolver->rr_cache,
					     question, allocator))
	{
	case GSK_DNS_LOCAL_NO_DATA:
	  break;
	case GSK_DNS_LOCAL_PARTIAL_DATA:
	case GSK_DNS_LOCAL_SUCCESS:
	  got_something = TRUE;
	  break;
	case GSK_DNS_LOCAL_NEGATIVE:
	  negatives = g_slist_prepend (negatives, question);
	  got_something = TRUE;
	  break;
	}
      questions = questions->next;
    }
  if (got_something)
    {
      negatives = g_slist_reverse (negatives);
      (*func) (allocator->answers,
	       allocator->authority,
	       allocator->additional,
	       negatives,
	       func_data);
      g_slist_free (negatives);
    }
  else
    {
      if (on_fail != NULL)
	{
	  GError *error = g_error_new (GSK_G_ERROR_DOMAIN,
				       GSK_ERROR_RESOLVER_NO_DATA,
				       _("no valid resources were found"));
	  (*on_fail) (error, func_data);
	  g_error_free (error);
	}
    }
  if (destroy != NULL)
    (*destroy) (func_data);
  return NULL;
}
#endif

static void
gsk_dns_local_resolver_get_property (GObject        *object,
			             guint           property_id,
			             GValue         *value,
			             GParamSpec     *pspec)
{
  switch (property_id)
    {
    case PROP_RR_CACHE:
      g_value_set_boxed (value, GSK_DNS_LOCAL_RESOLVER (object)->rr_cache);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gsk_dns_local_resolver_set_property (GObject        *object,
			             guint           property_id,
			             const GValue   *value,
			             GParamSpec     *pspec)
{
  GskDnsLocalResolver *local_resolver = GSK_DNS_LOCAL_RESOLVER (object);
  switch (property_id)
    {
    case PROP_RR_CACHE:
      {
	GskDnsRRCache *rr_cache = g_value_get_boxed (value);
	if (rr_cache)
	  gsk_dns_rr_cache_ref (rr_cache);
	if (local_resolver->rr_cache)
	  gsk_dns_rr_cache_unref (local_resolver->rr_cache);
	local_resolver->rr_cache = rr_cache;
	break;
      }
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gsk_dns_local_resolver_init (GskDnsLocalResolver *resolver)
{
  resolver->rr_cache = NULL;
}

#if IMPLEMENT_DNS_RESOLVER_IFACE
static void
gsk_dns_local_resolver_resolver_init (GskDnsResolverIface *iface)
{
  iface->resolve = gsk_dns_local_resolver_real_resolve;
  iface->cancel = gsk_dns_local_resolver_resolve_cancel;
}
#endif

static void
gsk_dns_local_resolver_class_init (GObjectClass *object_class)
{
  GParamSpec *pspec;
#if 0
  static GInterfaceInfo client_resolver_info =
  {
    (GInterfaceInitFunc) gsk_dns_local_resolver_resolver_init,
    NULL,			/* interface_finalize */
    NULL			/* interface_data */
  };
#endif
  parent_class = g_type_class_peek_parent (object_class);
  object_class->finalize = gsk_dns_local_resolver_finalize;
  object_class->get_property = gsk_dns_local_resolver_get_property;
  object_class->set_property = gsk_dns_local_resolver_set_property;

#if IMPLEMENT_DNS_RESOLVER_IFACE
  g_type_add_interface_static (G_OBJECT_CLASS_TYPE (object_class),
			       GSK_TYPE_DNS_RESOLVER,
			       &client_resolver_info);
#endif

  pspec = g_param_spec_boxed ("resource-cache",
			      _("Resource Record Cache"),
			      _("cache of resource data used to answer queries"),
			      GSK_TYPE_DNS_RR_CACHE,
			      G_PARAM_READWRITE);
  g_object_class_install_property (object_class, PROP_RR_CACHE, pspec);
}

GType
gsk_dns_local_resolver_get_type()
{
  static GType dns_local_resolver_type = 0;
  if (!dns_local_resolver_type)
    {
      static const GTypeInfo dns_local_resolver_info =
      {
	sizeof(GskDnsLocalResolverClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gsk_dns_local_resolver_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GskDnsLocalResolver),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gsk_dns_local_resolver_init,
	NULL		/* value_table */
      };
      dns_local_resolver_type = g_type_register_static (G_TYPE_OBJECT,
                                                  "GskDnsLocalResolver",
						  &dns_local_resolver_info, 0);
    }
  return dns_local_resolver_type;
}

/**
 * gsk_dns_local_resolver_new:
 * @rr_cache: resource-record cache to use.
 *
 * Create a new local resolver based on an optional resource-record cache.
 *
 * returns: the newly allocated resolver.
 */
GskDnsResolver *
gsk_dns_local_resolver_new   (GskDnsRRCache *rr_cache)
{
  GskDnsLocalResolver *resolver;
  if (rr_cache)
    resolver = g_object_new (GSK_TYPE_DNS_LOCAL_RESOLVER,
			     "resource-cache", rr_cache,
			     NULL);
  else
    resolver = g_object_new (GSK_TYPE_DNS_LOCAL_RESOLVER, NULL);
  return GSK_DNS_RESOLVER (resolver);
}



syntax highlighted by Code2HTML, v. 0.9.1