[glib] Add support for MX, TXT, NS and SOA records to GResolver



commit 666374c16f3d8118fe3422839d22ca32af69e4d0
Author: Stef Walter <stefw gnome org>
Date:   Wed Apr 4 17:13:10 2012 +0200

    Add support for MX, TXT, NS and SOA records to GResolver
    
     * Add resolver functions for looking up DNS records of
       various types. Currently implemented: MX, TXT, SOA, SRV, NS
     * Return records as GVariant tuples.
     * Make the GSrvTarget lookups a wrapper over this new
       functionality.
     * Rework the resolver test so that it has support for
       looking up MX, NS, SOA, TXT records, and uses GOptionContext
    
    https://bugzilla.gnome.org/show_bug.cgi?id=672944

 docs/reference/gio/gio-sections.txt |    4 +
 gio/gio.symbols                     |    4 +
 gio/gioenums.h                      |   43 ++++
 gio/gnetworkingprivate.h            |   10 +-
 gio/gresolver.c                     |  469 ++++++++++++++++++++++++++++++++--
 gio/gresolver.h                     |   35 +++-
 gio/gthreadedresolver.c             |  118 ++++++----
 gio/tests/resolver.c                |  281 +++++++++++++++++++--
 8 files changed, 862 insertions(+), 102 deletions(-)
---
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index eee39bb..14b01c4 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -1699,6 +1699,9 @@ g_resolver_lookup_service
 g_resolver_lookup_service_async
 g_resolver_lookup_service_finish
 g_resolver_free_targets
+g_resolver_lookup_records
+g_resolver_lookup_records_async
+g_resolver_lookup_records_finish
 
 <SUBSECTION>
 G_RESOLVER_ERROR
@@ -1717,6 +1720,7 @@ G_TYPE_RESOLVER
 GResolverPrivate
 g_resolver_get_type
 g_resolver_error_quark
+g_resolver_record_type_get_type
 </SECTION>
 
 <SECTION>
diff --git a/gio/gio.symbols b/gio/gio.symbols
index 96b6875..0efb08e 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1713,3 +1713,7 @@ g_resources_unregister
 g_static_resource_fini
 g_static_resource_get_resource
 g_static_resource_init
+g_resolver_lookup_records
+g_resolver_lookup_records_async
+g_resolver_lookup_records_finish
+g_resolver_record_type_get_type
diff --git a/gio/gioenums.h b/gio/gioenums.h
index dd97230..cc19d3c 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -631,6 +631,49 @@ typedef enum {
 } GResolverError;
 
 /**
+ * GResolverRecordType:
+ * @G_RESOLVER_RECORD_SRV: lookup DNS SRV records for a domain
+ * @G_RESOLVER_RECORD_MX: lookup DNS MX records for a domain
+ * @G_RESOLVER_RECORD_TXT: lookup DNS TXT records for a name
+ * @G_RESOLVER_RECORD_SOA: lookup DNS SOA records for a zone
+ * @G_RESOLVER_RECORD_NS: lookup DNS NS records for a domain
+ *
+ * The type of record that g_resolver_lookup_records() or
+ * g_resolver_lookup_records_async() should retrieve. The records are returned
+ * as lists of #GVariant tuples. Each record type has different values in
+ * the variant tuples returned.
+ *
+ * %G_RESOLVER_RECORD_SRV records are returned as variants with the signature
+ * '(qqqs)', containing a guint16 with the priority, a guint16 with the
+ * weight, a guint16 with the port, and a string of the hostname.
+ *
+ * %G_RESOLVER_RECORD_MX records are returned as variants with the signature
+ * '(qs)', representing a guint16 with the preference, and a string containing
+ * the mail exchanger hostname.
+ *
+ * %G_RESOLVER_RECORD_TXT records are returned as variants with the signature
+ * '(as)', representing an array of the strings in the text record.
+ *
+ * %G_RESOLVER_RECORD_SOA records are returned as variants with the signature
+ * '(ssuuuuu)', representing a string containing the primary name server, a
+ * string containing the administrator, the serial as a guint32, the refresh
+ * interval as guint32, the retry interval as a guint32, the expire timeout
+ * as a guint32, and the ttl as a guint32.
+ *
+ * %G_RESOLVER_RECORD_NS records are returned as variants with the signature
+ * '(s)', representing a string of the hostname of the name server.
+ *
+ * Since: 2.34
+ */
+typedef enum {
+  G_RESOLVER_RECORD_SRV = 1,
+  G_RESOLVER_RECORD_MX,
+  G_RESOLVER_RECORD_TXT,
+  G_RESOLVER_RECORD_SOA,
+  G_RESOLVER_RECORD_NS
+} GResolverRecordType;
+
+/**
  * GResourceError:
  * @G_RESOURCE_ERROR_NOT_FOUND: no file was found at the requested path
  * @G_RESOURCE_ERROR_INTERNAL: unknown error
diff --git a/gio/gnetworkingprivate.h b/gio/gnetworkingprivate.h
index ce31b1e..2be3688 100644
--- a/gio/gnetworkingprivate.h
+++ b/gio/gnetworkingprivate.h
@@ -95,13 +95,19 @@ char  *_g_resolver_name_from_nameinfo      (GInetAddress     *address,
 					    GError          **error);
 
 #if defined(G_OS_UNIX)
-GList *_g_resolver_targets_from_res_query  (const gchar      *rrname,
+gint   _g_resolver_record_type_to_rrtype   (GResolverRecordType record_type);
+
+GList *_g_resolver_records_from_res_query  (const gchar      *rrname,
+					    gint              rrtype,
 					    guchar           *answer,
 					    gint              len,
 					    gint              herr,
 					    GError          **error);
 #elif defined(G_OS_WIN32)
-GList *_g_resolver_targets_from_DnsQuery   (const gchar      *rrname,
+WORD   _g_resolver_record_type_to_dnstype  (GResolverRecordType record_type);
+
+GList *_g_resolver_records_from_DnsQuery   (const gchar      *rrname,
+					    WORD              dnstype,
 					    DNS_STATUS        status,
 					    DNS_RECORD       *results,
 					    GError          **error);
diff --git a/gio/gresolver.c b/gio/gresolver.c
index ad06974..9f80fc0 100644
--- a/gio/gresolver.c
+++ b/gio/gresolver.c
@@ -78,11 +78,81 @@ struct _GResolverPrivate {
  */
 G_DEFINE_TYPE (GResolver, g_resolver, G_TYPE_OBJECT)
 
+static GList *
+srv_records_to_targets (GList *records)
+{
+  const gchar *hostname;
+  guint16 port, priority, weight;
+  GSrvTarget *target;
+  GList *l;
+
+  for (l = records; l != NULL; l = g_list_next (l))
+    {
+      g_variant_get (l->data, "(qqq&s)", &priority, &weight, &port, &hostname);
+      target = g_srv_target_new (hostname, port, priority, weight);
+      g_variant_unref (l->data);
+      l->data = target;
+    }
+
+  return g_srv_target_list_sort (records);
+}
+
+static GList *
+g_resolver_real_lookup_service (GResolver            *resolver,
+                                const gchar          *rrname,
+                                GCancellable         *cancellable,
+                                GError              **error)
+{
+  GList *records;
+
+  records = G_RESOLVER_GET_CLASS (resolver)->lookup_records (resolver,
+                                                             rrname,
+                                                             G_RESOLVER_RECORD_SRV,
+                                                             cancellable,
+                                                             error);
+
+  return srv_records_to_targets (records);
+}
+
+static void
+g_resolver_real_lookup_service_async (GResolver            *resolver,
+                                      const gchar          *rrname,
+                                      GCancellable         *cancellable,
+                                      GAsyncReadyCallback   callback,
+                                      gpointer              user_data)
+{
+  G_RESOLVER_GET_CLASS (resolver)->lookup_records_async (resolver,
+                                                         rrname,
+                                                         G_RESOLVER_RECORD_SRV,
+                                                         cancellable,
+                                                         callback,
+                                                         user_data);
+}
+
+static GList *
+g_resolver_real_lookup_service_finish (GResolver            *resolver,
+                                       GAsyncResult         *result,
+                                       GError              **error)
+{
+  GList *records;
+
+  records = G_RESOLVER_GET_CLASS (resolver)->lookup_records_finish (resolver,
+                                                                    result,
+                                                                    error);
+
+  return srv_records_to_targets (records);
+}
+
 static void
 g_resolver_class_init (GResolverClass *resolver_class)
 {
   volatile GType type;
 
+  /* Automatically pass these over to the lookup_records methods */
+  resolver_class->lookup_service = g_resolver_real_lookup_service;
+  resolver_class->lookup_service_async = g_resolver_real_lookup_service_async;
+  resolver_class->lookup_service_finish = g_resolver_real_lookup_service_finish;
+
   g_type_class_add_private (resolver_class, sizeof (GResolverPrivate));
 
   /* Make sure _g_networking_init() has been called */
@@ -708,6 +778,112 @@ g_resolver_free_targets (GList *targets)
 }
 
 /**
+ * g_resolver_lookup_records:
+ * @resolver: a #GResolver
+ * @rrname: the DNS name to lookup the record for
+ * @record_type: the type of DNS record to lookup
+ * @cancellable: (allow-none): a #GCancellable, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously performs a DNS record lookup for the given @rrname and returns
+ * a list of records as #GVariant tuples. See #GResolverRecordType for
+ * information on what the records contain for each @record_type.
+ *
+ * If the DNS resolution fails, @error (if non-%NULL) will be set to
+ * a value from #GResolverError.
+ *
+ * If @cancellable is non-%NULL, it can be used to cancel the
+ * operation, in which case @error (if non-%NULL) will be set to
+ * %G_IO_ERROR_CANCELLED.
+ *
+ * Return value: (element-type GVariant) (transfer full): a #GList of #GVariant,
+ * or %NULL on error. You must free each of the records and the list when you are
+ * done with it. (You can use g_list_free_full() with g_variant_unref() to do this.)
+ *
+ * Since: 2.34
+ */
+GList *
+g_resolver_lookup_records (GResolver            *resolver,
+                           const gchar          *rrname,
+                           GResolverRecordType   record_type,
+                           GCancellable         *cancellable,
+                           GError              **error)
+{
+  GList *records;
+
+  g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
+  g_return_val_if_fail (rrname != NULL, NULL);
+
+  g_resolver_maybe_reload (resolver);
+  records = G_RESOLVER_GET_CLASS (resolver)->
+    lookup_records (resolver, rrname, record_type, cancellable, error);
+
+  return records;
+}
+
+/**
+ * g_resolver_lookup_records_async:
+ * @resolver: a #GResolver
+ * @rrname: the DNS name to lookup the record for
+ * @record_type: the type of DNS record to lookup
+ * @cancellable: (allow-none): a #GCancellable, or %NULL
+ * @callback: (scope async): callback to call after resolution completes
+ * @user_data: (closure): data for @callback
+ *
+ * Begins asynchronously performing a DNS lookup for the given
+ * @rrname, and eventually calls @callback, which must call
+ * g_resolver_lookup_records_finish() to get the final result. See
+ * g_resolver_lookup_records() for more details.
+ *
+ * Since: 2.34
+ */
+void
+g_resolver_lookup_records_async (GResolver           *resolver,
+                                 const gchar         *rrname,
+                                 GResolverRecordType  record_type,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_return_if_fail (G_IS_RESOLVER (resolver));
+  g_return_if_fail (rrname != NULL);
+
+  g_resolver_maybe_reload (resolver);
+  G_RESOLVER_GET_CLASS (resolver)->
+    lookup_records_async (resolver, rrname, record_type, cancellable, callback, user_data);
+}
+
+/**
+ * g_resolver_lookup_records_finish:
+ * @resolver: a #GResolver
+ * @result: the result passed to your #GAsyncReadyCallback
+ * @error: return location for a #GError, or %NULL
+ *
+ * Retrieves the result of a previous call to
+ * g_resolver_lookup_records_async(). Returns a list of records as #GVariant
+ * tuples. See #GResolverRecordType for information on what the records contain.
+ *
+ * If the DNS resolution failed, @error (if non-%NULL) will be set to
+ * a value from #GResolverError. If the operation was cancelled,
+ * @error will be set to %G_IO_ERROR_CANCELLED.
+ *
+ * Return value: (element-type GVariant) (transfer full): a #GList of #GVariant,
+ * or %NULL on error. You must free each of the records and the list when you are
+ * done with it. (You can use g_list_free_full() with g_variant_unref() to do this.)
+ *
+ * Since: 2.34
+ */
+GList *
+g_resolver_lookup_records_finish (GResolver     *resolver,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+  g_return_val_if_fail (G_IS_RESOLVER (resolver), NULL);
+  return G_RESOLVER_GET_CLASS (resolver)->
+    lookup_records_finish (resolver, result, error);
+}
+
+/**
  * g_resolver_error_quark:
  *
  * Gets the #GResolver Error Quark.
@@ -821,9 +997,133 @@ _g_resolver_name_from_nameinfo (GInetAddress  *address,
 }
 
 #if defined(G_OS_UNIX)
+static GVariant *
+parse_res_srv (guchar  *answer,
+               guchar  *end,
+               guchar **p)
+{
+  gchar namebuf[1024];
+  guint16 priority, weight, port;
+
+  GETSHORT (priority, *p);
+  GETSHORT (weight, *p);
+  GETSHORT (port, *p);
+  *p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf));
+
+  return g_variant_new ("(qqqs)",
+                        priority,
+                        weight,
+                        port,
+                        namebuf);
+}
+
+static GVariant *
+parse_res_soa (guchar  *answer,
+               guchar  *end,
+               guchar **p)
+{
+  gchar mnamebuf[1024];
+  gchar rnamebuf[1024];
+  guint32 serial, refresh, retry, expire, ttl;
+
+  *p += dn_expand (answer, end, *p, mnamebuf, sizeof (mnamebuf));
+  *p += dn_expand (answer, end, *p, rnamebuf, sizeof (rnamebuf));
+
+  GETLONG (serial, *p);
+  GETLONG (refresh, *p);
+  GETLONG (retry, *p);
+  GETLONG (expire, *p);
+  GETLONG (ttl, *p);
+
+  return g_variant_new ("(ssuuuuu)",
+                        mnamebuf,
+                        rnamebuf,
+                        serial,
+                        refresh,
+                        retry,
+                        expire,
+                        ttl);
+}
+
+static GVariant *
+parse_res_ns (guchar  *answer,
+              guchar  *end,
+              guchar **p)
+{
+  gchar namebuf[1024];
+
+  *p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf));
+
+  return g_variant_new ("(s)", namebuf);
+}
+
+static GVariant *
+parse_res_mx (guchar  *answer,
+              guchar  *end,
+              guchar **p)
+{
+  gchar namebuf[1024];
+  guint16 preference;
+
+  GETSHORT (preference, *p);
+
+  *p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf));
+
+  return g_variant_new ("(qs)",
+                        preference,
+                        namebuf);
+}
+
+static GVariant *
+parse_res_txt (guchar  *answer,
+               guchar  *end,
+               guchar **p)
+{
+  GVariant *record;
+  GPtrArray *array;
+  guchar *at = *p;
+  gsize len;
+
+  array = g_ptr_array_new_with_free_func (g_free);
+  while (at < end)
+    {
+      len = *(at++);
+      if (len > at - end)
+        break;
+      g_ptr_array_add (array, g_strndup ((gchar *)at, len));
+      at += len;
+    }
+
+  *p = at;
+  record = g_variant_new ("(@as)",
+                          g_variant_new_strv ((const gchar **)array->pdata, array->len));
+  g_ptr_array_free (array, TRUE);
+  return record;
+}
+
+gint
+_g_resolver_record_type_to_rrtype (GResolverRecordType type)
+{
+  switch (type)
+  {
+    case G_RESOLVER_RECORD_SRV:
+      return T_SRV;
+    case G_RESOLVER_RECORD_TXT:
+      return T_TXT;
+    case G_RESOLVER_RECORD_SOA:
+      return T_SOA;
+    case G_RESOLVER_RECORD_NS:
+      return T_NS;
+    case G_RESOLVER_RECORD_MX:
+      return T_MX;
+  }
+  g_return_val_if_reached (-1);
+}
+
 /* Private method to process a res_query response into GSrvTargets */
 GList *
-_g_resolver_targets_from_res_query (const gchar      *rrname,
+_g_resolver_records_from_res_query (const gchar      *rrname,
+                                    gint              rrtype,
                                     guchar           *answer,
                                     gint              len,
                                     gint              herr,
@@ -832,11 +1132,11 @@ _g_resolver_targets_from_res_query (const gchar      *rrname,
   gint count;
   gchar namebuf[1024];
   guchar *end, *p;
-  guint16 type, qclass, rdlength, priority, weight, port;
+  guint16 type, qclass, rdlength;
   guint32 ttl;
   HEADER *header;
-  GSrvTarget *target;
-  GList *targets;
+  GList *records;
+  GVariant *record;
 
   if (len <= 0)
     {
@@ -846,7 +1146,7 @@ _g_resolver_targets_from_res_query (const gchar      *rrname,
       if (len == 0 || herr == HOST_NOT_FOUND || herr == NO_DATA)
         {
           errnum = G_RESOLVER_ERROR_NOT_FOUND;
-          format = _("No service record for '%s'");
+          format = _("No DNS record of the requested type for '%s'");
         }
       else if (herr == TRY_AGAIN)
         {
@@ -863,7 +1163,7 @@ _g_resolver_targets_from_res_query (const gchar      *rrname,
       return NULL;
     }
 
-  targets = NULL;
+  records = NULL;
 
   header = (HEADER *)answer;
   p = answer + sizeof (HEADER);
@@ -875,6 +1175,9 @@ _g_resolver_targets_from_res_query (const gchar      *rrname,
     {
       p += dn_expand (answer, end, p, namebuf, sizeof (namebuf));
       p += 4;
+
+      /* To silence gcc warnings */
+      namebuf[0] = namebuf[1];
     }
 
   /* Read answers */
@@ -888,34 +1191,126 @@ _g_resolver_targets_from_res_query (const gchar      *rrname,
       ttl = ttl; /* To avoid -Wunused-but-set-variable */
       GETSHORT (rdlength, p);
 
-      if (type != T_SRV || qclass != C_IN)
+      if (type != rrtype || qclass != C_IN)
         {
           p += rdlength;
           continue;
         }
 
-      GETSHORT (priority, p);
-      GETSHORT (weight, p);
-      GETSHORT (port, p);
-      p += dn_expand (answer, end, p, namebuf, sizeof (namebuf));
+      switch (rrtype)
+        {
+        case T_SRV:
+          record = parse_res_srv (answer, end, &p);
+          break;
+        case T_MX:
+          record = parse_res_mx (answer, end, &p);
+          break;
+        case T_SOA:
+          record = parse_res_soa (answer, end, &p);
+          break;
+        case T_NS:
+          record = parse_res_ns (answer, end, &p);
+          break;
+        case T_TXT:
+          record = parse_res_txt (answer, p + rdlength, &p);
+          break;
+        default:
+          g_warn_if_reached ();
+          record = NULL;
+          break;
+        }
 
-      target = g_srv_target_new (namebuf, port, priority, weight);
-      targets = g_list_prepend (targets, target);
+      if (record != NULL)
+        records = g_list_prepend (records, record);
     }
 
-  return g_srv_target_list_sort (targets);
+    return records;
 }
+
 #elif defined(G_OS_WIN32)
-/* Private method to process a DnsQuery response into GSrvTargets */
+static GVariant *
+parse_dns_srv (DNS_RECORD *rec)
+{
+  return g_variant_new ("(qqqs)",
+                        (guint16)rec->Data.SRV.wPriority,
+                        (guint16)rec->Data.SRV.wWeight,
+                        (guint16)rec->Data.SRV.wPort,
+                        rec->Data.SRV.pNameTarget);
+}
+
+static GVariant *
+parse_dns_soa (DNS_RECORD *rec)
+{
+  return g_variant_new ("(ssuuuuu)",
+                        rec->Data.SOA.pNamePrimaryServer,
+                        rec->Data.SOA.pNameAdministrator,
+                        (guint32)rec->Data.SOA.dwSerialNo,
+                        (guint32)rec->Data.SOA.dwRefresh,
+                        (guint32)rec->Data.SOA.dwRetry,
+                        (guint32)rec->Data.SOA.dwExpire,
+                        (guint32)rec->Data.SOA.dwDefaultTtl);
+}
+
+static GVariant *
+parse_dns_ns (DNS_RECORD *rec)
+{
+  return g_variant_new ("(s)", rec->Data.NS.pNameHost);
+}
+
+static GVariant *
+parse_dns_mx (DNS_RECORD *rec)
+{
+  return g_variant_new ("(qs)",
+                        (guint16)rec->Data.MX.wPreference,
+                        rec->Data.MX.pNameExchange);
+}
+
+static GVariant *
+parse_dns_txt (DNS_RECORD *rec)
+{
+  GVariant *record;
+  GPtrArray *array;
+  DWORD i;
+
+  array = g_ptr_array_new ();
+  for (i = 0; i < rec->Data.TXT.dwStringCount; i++)
+    g_ptr_array_add (array, rec->Data.TXT.pStringArray[i]);
+  record = g_variant_new ("(@as)",
+                          g_variant_new_strv ((const gchar **)array->pdata, array->len));
+  g_ptr_array_free (array, TRUE);
+  return record;
+}
+
+WORD
+_g_resolver_record_type_to_dnstype (GResolverRecordType type)
+{
+  switch (type)
+  {
+    case G_RESOLVER_RECORD_SRV:
+      return DNS_TYPE_SRV;
+    case G_RESOLVER_RECORD_TXT:
+      return DNS_TYPE_TEXT;
+    case G_RESOLVER_RECORD_SOA:
+      return DNS_TYPE_SOA;
+    case G_RESOLVER_RECORD_NS:
+      return DNS_TYPE_NS;
+    case G_RESOLVER_RECORD_MX:
+      return DNS_TYPE_MX;
+  }
+  g_return_val_if_reached (-1);
+}
+
+/* Private method to process a DnsQuery response into GVariants */
 GList *
-_g_resolver_targets_from_DnsQuery (const gchar  *rrname,
+_g_resolver_records_from_DnsQuery (const gchar  *rrname,
+                                   WORD          dnstype,
                                    DNS_STATUS    status,
                                    DNS_RECORD   *results,
                                    GError      **error)
 {
   DNS_RECORD *rec;
-  GSrvTarget *target;
-  GList *targets;
+  gpointer record;
+  GList *records;
 
   if (status != ERROR_SUCCESS)
     {
@@ -925,7 +1320,7 @@ _g_resolver_targets_from_DnsQuery (const gchar  *rrname,
       if (status == DNS_ERROR_RCODE_NAME_ERROR)
         {
           errnum = G_RESOLVER_ERROR_NOT_FOUND;
-          format = _("No service record for '%s'");
+          format = _("No DNS record of the requested type for '%s'");
         }
       else if (status == DNS_ERROR_RCODE_SERVER_FAILURE)
         {
@@ -942,20 +1337,38 @@ _g_resolver_targets_from_DnsQuery (const gchar  *rrname,
       return NULL;
     }
 
-  targets = NULL;
+  records = NULL;
   for (rec = results; rec; rec = rec->pNext)
     {
-      if (rec->wType != DNS_TYPE_SRV)
+      if (rec->wType != dnstype)
         continue;
-
-      target = g_srv_target_new (rec->Data.SRV.pNameTarget,
-                                 rec->Data.SRV.wPort,
-                                 rec->Data.SRV.wPriority,
-                                 rec->Data.SRV.wWeight);
-      targets = g_list_prepend (targets, target);
+      switch (dnstype)
+        {
+        case DNS_TYPE_SRV:
+          record = parse_dns_srv (rec);
+          break;
+        case DNS_TYPE_SOA:
+          record = parse_dns_soa (rec);
+          break;
+        case DNS_TYPE_NS:
+          record = parse_dns_ns (rec);
+          break;
+        case DNS_TYPE_MX:
+          record = parse_dns_mx (rec);
+          break;
+        case DNS_TYPE_TEXT:
+          record = parse_dns_txt (rec);
+          break;
+        default:
+          g_warn_if_reached ();
+          record = NULL;
+          break;
+        }
+      if (record != NULL)
+        records = g_list_prepend (records, g_variant_ref_sink (record));
     }
 
-  return g_srv_target_list_sort (targets);
+  return records;
 }
 
 #endif
diff --git a/gio/gresolver.h b/gio/gresolver.h
index 1187a3f..dcadf96 100644
--- a/gio/gresolver.h
+++ b/gio/gresolver.h
@@ -91,10 +91,24 @@ struct _GResolverClass {
 					GAsyncResult         *result,
 					GError              **error);
 
+  GList * ( *lookup_records)           (GResolver            *resolver,
+                                        const gchar          *rrname,
+                                        GResolverRecordType   record_type,
+                                        GCancellable         *cancellable,
+                                        GError              **error);
+
+  void    ( *lookup_records_async)     (GResolver            *resolver,
+                                        const gchar          *rrname,
+                                        GResolverRecordType   record_type,
+                                        GCancellable         *cancellable,
+                                        GAsyncReadyCallback   callback,
+                                        gpointer              user_data);
+
+  GList * ( *lookup_records_finish)    (GResolver            *resolver,
+                                        GAsyncResult         *result,
+                                        GError              **error);
+
   /* Padding for future expansion */
-  void (*_g_reserved1) (void);
-  void (*_g_reserved2) (void);
-  void (*_g_reserved3) (void);
   void (*_g_reserved4) (void);
   void (*_g_reserved5) (void);
   void (*_g_reserved6) (void);
@@ -150,6 +164,21 @@ GList     *g_resolver_lookup_service_finish     (GResolver            *resolver,
 						 GAsyncResult         *result,
 						 GError              **error);
 
+GList     *g_resolver_lookup_records            (GResolver            *resolver,
+                                                 const gchar          *rrname,
+                                                 GResolverRecordType   record_type,
+                                                 GCancellable         *cancellable,
+                                                 GError              **error);
+void       g_resolver_lookup_records_async      (GResolver            *resolver,
+                                                 const gchar          *rrname,
+                                                 GResolverRecordType   record_type,
+                                                 GCancellable         *cancellable,
+                                                 GAsyncReadyCallback   callback,
+                                                 gpointer              user_data);
+GList     *g_resolver_lookup_records_finish     (GResolver            *resolver,
+                                                 GAsyncResult         *result,
+                                                 GError              **error);
+
 void       g_resolver_free_targets              (GList                *targets);
 
 /**
diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c
index 5a820be..73cb6e8 100644
--- a/gio/gthreadedresolver.c
+++ b/gio/gthreadedresolver.c
@@ -151,8 +151,9 @@ struct _GThreadedResolverRequest {
     } address;
     struct {
       gchar *rrname;
-      GList *targets;
-    } service;
+      GResolverRecordType record_type;
+      GList *results;
+    } records;
   } u;
 
   GCancellable *cancellable;
@@ -515,85 +516,108 @@ lookup_by_address_finish (GResolver     *resolver,
 
 
 static void
-do_lookup_service (GThreadedResolverRequest *req,
-                   GError   **error)
+do_lookup_records (GThreadedResolverRequest  *req,
+                   GError                   **error)
 {
 #if defined(G_OS_UNIX)
-  gint len, herr;
-  guchar answer[1024];
-#elif defined(G_OS_WIN32)
-  DNS_STATUS status;
-  DNS_RECORD *results;
-#endif
+  gint len = 512;
+  gint herr;
+  GByteArray *answer;
+  gint rrtype;
+
+  rrtype = _g_resolver_record_type_to_rrtype (req->u.records.record_type);
+  answer = g_byte_array_new ();
+  for (;;)
+    {
+      g_byte_array_set_size (answer, len * 2);
+      len = res_query (req->u.records.rrname, C_IN, rrtype, answer->data, answer->len);
+
+      /* If answer fit in the buffer then we're done */
+      if (len < 0 || len < (gint)answer->len)
+        break;
+
+      /*
+       * On overflow some res_query's return the length needed, others
+       * return the full length entered. This code works in either case.
+       */
+  }
 
-#if defined(G_OS_UNIX)
-  len = res_query (req->u.service.rrname, C_IN, T_SRV, answer, sizeof (answer));
   herr = h_errno;
-  req->u.service.targets = _g_resolver_targets_from_res_query (req->u.service.rrname, answer, len, herr, error);
+  req->u.records.results = _g_resolver_records_from_res_query (req->u.records.rrname, rrtype, answer->data, len, herr, error);
+  g_byte_array_free (answer, TRUE);
+
 #elif defined(G_OS_WIN32)
-  status = DnsQuery_A (req->u.service.rrname, DNS_TYPE_SRV,
-                       DNS_QUERY_STANDARD, NULL, &results, NULL);
-  req->u.service.targets = _g_resolver_targets_from_DnsQuery (req->u.service.rrname, status, results, error);
-  DnsRecordListFree (results, DnsFreeRecordList);
+  DNS_STATUS status;
+  DNS_RECORD *results = NULL;
+  WORD dnstype;
+
+  dnstype = _g_resolver_record_type_to_dnstype (req->u.records.record_type);
+  status = DnsQuery_A (req->u.records.rrname, dnstype, DNS_QUERY_STANDARD, NULL, &results, NULL);
+  req->u.records.results = _g_resolver_records_from_DnsQuery (req->u.records.rrname, dnstype, status, results, error);
+  if (results != NULL)
+    DnsRecordListFree (results, DnsFreeRecordList);
 #endif
 }
 
 static GList *
-lookup_service (GResolver        *resolver,
-                const gchar      *rrname,
-		GCancellable     *cancellable,
-                GError          **error)
+lookup_records (GResolver              *resolver,
+                const gchar            *rrname,
+                GResolverRecordType    record_type,
+                GCancellable          *cancellable,
+                GError               **error)
 {
   GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver);
   GThreadedResolverRequest *req;
-  GList *targets;
+  GList *results;
 
-  req = g_threaded_resolver_request_new (do_lookup_service, NULL, cancellable);
-  req->u.service.rrname = (char *)rrname;
+  req = g_threaded_resolver_request_new (do_lookup_records, NULL, cancellable);
+  req->u.records.rrname = (char *)rrname;
+  req->u.records.record_type = record_type;
   resolve_sync (gtr, req, error);
 
-  targets = req->u.service.targets;
+  results = req->u.records.results;
   g_threaded_resolver_request_unref (req);
-  return targets;
+  return results;
 }
 
 static void
-free_lookup_service (GThreadedResolverRequest *req)
+free_lookup_records (GThreadedResolverRequest *req)
 {
-  g_free (req->u.service.rrname);
-  if (req->u.service.targets)
-    g_resolver_free_targets (req->u.service.targets);
+  g_free (req->u.records.rrname);
+  g_list_free_full (req->u.records.results, (GDestroyNotify)g_variant_unref);
 }
 
 static void
-lookup_service_async (GResolver           *resolver,
+lookup_records_async (GResolver           *resolver,
                       const char          *rrname,
-		      GCancellable        *cancellable,
+                      GResolverRecordType  record_type,
+                      GCancellable        *cancellable,
                       GAsyncReadyCallback  callback,
-		      gpointer             user_data)
+                      gpointer             user_data)
 {
   GThreadedResolver *gtr = G_THREADED_RESOLVER (resolver);
   GThreadedResolverRequest *req;
 
-  req = g_threaded_resolver_request_new (do_lookup_service,
-                                         free_lookup_service,
+  req = g_threaded_resolver_request_new (do_lookup_records,
+                                         free_lookup_records,
                                          cancellable);
-  req->u.service.rrname = g_strdup (rrname);
-  resolve_async (gtr, req, callback, user_data, lookup_service_async);
+  req->u.records.rrname = g_strdup (rrname);
+  req->u.records.record_type = record_type;
+  resolve_async (gtr, req, callback, user_data, lookup_records_async);
 }
 
 static GList *
-lookup_service_finish (GResolver     *resolver,
+lookup_records_finish (GResolver     *resolver,
                        GAsyncResult  *result,
-		       GError       **error)
+                       GError       **error)
 {
   GThreadedResolverRequest *req;
-  GList *targets;
+  GList *records;
 
-  req = resolve_finish (resolver, result, lookup_service_async, error);
-  targets = req->u.service.targets;
-  req->u.service.targets = NULL;
-  return targets;
+  req = resolve_finish (resolver, result, lookup_records_async, error);
+  records = req->u.records.results;
+  req->u.records.results = NULL;
+  return records;
 }
 
 
@@ -609,9 +633,9 @@ g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class)
   resolver_class->lookup_by_address        = lookup_by_address;
   resolver_class->lookup_by_address_async  = lookup_by_address_async;
   resolver_class->lookup_by_address_finish = lookup_by_address_finish;
-  resolver_class->lookup_service           = lookup_service;
-  resolver_class->lookup_service_async     = lookup_service_async;
-  resolver_class->lookup_service_finish    = lookup_service_finish;
+  resolver_class->lookup_records           = lookup_records;
+  resolver_class->lookup_records_async     = lookup_records_async;
+  resolver_class->lookup_records_finish    = lookup_records_finish;
 
   object_class->finalize = finalize;
 }
diff --git a/gio/tests/resolver.c b/gio/tests/resolver.c
index 8c474b2..81a6df2 100644
--- a/gio/tests/resolver.c
+++ b/gio/tests/resolver.c
@@ -36,15 +36,20 @@ static GResolver *resolver;
 static GCancellable *cancellable;
 static GMainLoop *loop;
 static int nlookups = 0;
+static gboolean synchronous = FALSE;
+static guint connectable_count = 0;
+static GResolverRecordType record_type = 0;
 
 static void G_GNUC_NORETURN
 usage (void)
 {
 	fprintf (stderr, "Usage: resolver [-s] [hostname | IP | service/protocol/domain ] ...\n");
+	fprintf (stderr, "Usage: resolver [-s] [-t MX|TXT|NS|SOA] rrname ...\n");
 	fprintf (stderr, "       resolver [-s] -c NUMBER [hostname | IP | service/protocol/domain ]\n");
 	fprintf (stderr, "       Use -s to do synchronous lookups.\n");
 	fprintf (stderr, "       Use -c NUMBER (and only a single resolvable argument) to test GSocketConnectable.\n");
 	fprintf (stderr, "       The given NUMBER determines how many times the connectable will be enumerated.\n");
+	fprintf (stderr, "       Use -t with MX, TXT, NS or SOA to lookup DNS records of those types.\n");
 	exit (1);
 }
 
@@ -138,9 +143,9 @@ print_resolved_service (const char *service,
 	{
 	  printf ("%s:%u (pri %u, weight %u)\n",
 		  g_srv_target_get_hostname (t->data),
-		  g_srv_target_get_port (t->data),
-		  g_srv_target_get_priority (t->data),
-		  g_srv_target_get_weight (t->data));
+		  (guint)g_srv_target_get_port (t->data),
+		  (guint)g_srv_target_get_priority (t->data),
+		  (guint)g_srv_target_get_weight (t->data));
           g_srv_target_free (t->data);
 	}
       g_list_free (targets);
@@ -152,11 +157,185 @@ print_resolved_service (const char *service,
 }
 
 static void
+print_resolved_mx (const char *rrname,
+                   GList      *records,
+                   GError     *error)
+{
+  const gchar *hostname;
+  guint16 priority;
+  GList *t;
+
+  G_LOCK (response);
+  printf ("Domain: %s\n", rrname);
+  if (error)
+    {
+      printf ("Error: %s\n", error->message);
+      g_error_free (error);
+    }
+  else if (!records)
+    {
+      printf ("no MX records\n");
+    }
+  else
+    {
+      for (t = records; t; t = t->next)
+        {
+          g_variant_get (t->data, "(q&s)", &priority, &hostname);
+          printf ("%s (pri %u)\n", hostname, (guint)priority);
+          g_variant_unref (t->data);
+        }
+      g_list_free (records);
+    }
+  printf ("\n");
+
+  done_lookup ();
+  G_UNLOCK (response);
+}
+
+static void
+print_resolved_txt (const char *rrname,
+                    GList      *records,
+                    GError     *error)
+{
+  const gchar **contents;
+  GList *t;
+  gint i;
+
+  G_LOCK (response);
+  printf ("Domain: %s\n", rrname);
+  if (error)
+    {
+      printf ("Error: %s\n", error->message);
+      g_error_free (error);
+    }
+  else if (!records)
+    {
+      printf ("no TXT records\n");
+    }
+  else
+    {
+      for (t = records; t; t = t->next)
+        {
+          if (t != records)
+            printf ("\n");
+          g_variant_get (t->data, "(^a&s)", &contents);
+          for (i = 0; contents[i] != NULL; i++)
+            printf ("%s\n", contents[i]);
+          g_variant_unref (t->data);
+        }
+      g_list_free (records);
+    }
+  printf ("\n");
+
+  done_lookup ();
+  G_UNLOCK (response);
+}
+
+static void
+print_resolved_soa (const char *rrname,
+                    GList      *records,
+                    GError     *error)
+{
+  GList *t;
+  const gchar *primary_ns;
+  const gchar *administrator;
+  guint32 serial, refresh, retry, expire, ttl;
+
+  G_LOCK (response);
+  printf ("Zone: %s\n", rrname);
+  if (error)
+    {
+      printf ("Error: %s\n", error->message);
+      g_error_free (error);
+    }
+  else if (!records)
+    {
+      printf ("no SOA records\n");
+    }
+  else
+    {
+      for (t = records; t; t = t->next)
+        {
+          g_variant_get (t->data, "(&s&suuuuu)", &primary_ns, &administrator,
+                         &serial, &refresh, &retry, &expire, &ttl);
+          printf ("%s %s (serial %u, refresh %u, retry %u, expire %u, ttl %u)\n",
+                  primary_ns, administrator, (guint)serial, (guint)refresh,
+                  (guint)retry, (guint)expire, (guint)ttl);
+          g_variant_unref (t->data);
+        }
+      g_list_free (records);
+    }
+  printf ("\n");
+
+  done_lookup ();
+  G_UNLOCK (response);
+}
+
+static void
+print_resolved_ns (const char *rrname,
+                    GList      *records,
+                    GError     *error)
+{
+  GList *t;
+  const gchar *hostname;
+
+  G_LOCK (response);
+  printf ("Zone: %s\n", rrname);
+  if (error)
+    {
+      printf ("Error: %s\n", error->message);
+      g_error_free (error);
+    }
+  else if (!records)
+    {
+      printf ("no NS records\n");
+    }
+  else
+    {
+      for (t = records; t; t = t->next)
+        {
+          g_variant_get (t->data, "(&s)", &hostname);
+          printf ("%s\n", hostname);
+          g_variant_unref (t->data);
+        }
+      g_list_free (records);
+    }
+  printf ("\n");
+
+  done_lookup ();
+  G_UNLOCK (response);
+}
+
+static void
 lookup_one_sync (const char *arg)
 {
   GError *error = NULL;
 
-  if (strchr (arg, '/'))
+  if (record_type != 0)
+    {
+      GList *records;
+
+      records = g_resolver_lookup_records (resolver, arg, record_type, cancellable, &error);
+      switch (record_type)
+      {
+        case G_RESOLVER_RECORD_MX:
+          print_resolved_mx (arg, records, error);
+          break;
+        case G_RESOLVER_RECORD_SOA:
+          print_resolved_soa (arg, records, error);
+          break;
+        case G_RESOLVER_RECORD_NS:
+          print_resolved_ns (arg, records, error);
+          break;
+        case G_RESOLVER_RECORD_TXT:
+          print_resolved_txt (arg, records, error);
+          break;
+        default:
+          g_warn_if_reached ();
+          break;
+      }
+    }
+  else if (strchr (arg, '/'))
     {
       GList *targets;
       /* service/protocol/domain */
@@ -245,13 +424,49 @@ lookup_service_callback (GObject *source, GAsyncResult *result,
 }
 
 static void
+lookup_records_callback (GObject      *source,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  const char *arg = user_data;
+  GError *error = NULL;
+  GList *records;
+
+  records = g_resolver_lookup_records_finish (resolver, result, &error);
+
+  switch (record_type)
+  {
+    case G_RESOLVER_RECORD_MX:
+      print_resolved_mx (arg, records, error);
+      break;
+    case G_RESOLVER_RECORD_SOA:
+      print_resolved_soa (arg, records, error);
+      break;
+    case G_RESOLVER_RECORD_NS:
+      print_resolved_ns (arg, records, error);
+      break;
+    case G_RESOLVER_RECORD_TXT:
+      print_resolved_txt (arg, records, error);
+      break;
+    default:
+      g_warn_if_reached ();
+      break;
+  }
+}
+
+static void
 start_async_lookups (char **argv, int argc)
 {
   int i;
 
   for (i = 0; i < argc; i++)
     {
-      if (strchr (argv[i], '/'))
+      if (record_type != 0)
+	{
+	  g_resolver_lookup_records_async (resolver, argv[i], record_type,
+	                                   cancellable, lookup_records_callback, argv[i]);
+	}
+      else if (strchr (argv[i], '/'))
 	{
 	  /* service/protocol/domain */
 	  char **parts = g_strsplit (argv[i], "/", 3);
@@ -428,11 +643,42 @@ async_cancel (GIOChannel *source, GIOCondition cond, gpointer cancel)
 }
 #endif
 
+
+static gboolean
+record_type_arg (const gchar *option_name,
+                 const gchar *value,
+                 gpointer data,
+                 GError **error)
+{
+  if (g_ascii_strcasecmp (value, "MX") == 0) {
+    record_type = G_RESOLVER_RECORD_MX;
+  } else if (g_ascii_strcasecmp (value, "TXT") == 0) {
+    record_type = G_RESOLVER_RECORD_TXT;
+  } else if (g_ascii_strcasecmp (value, "SOA") == 0) {
+    record_type = G_RESOLVER_RECORD_SOA;
+  } else if (g_ascii_strcasecmp (value, "NS") == 0) {
+    record_type = G_RESOLVER_RECORD_NS;
+  } else {
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                   "Specify MX, TXT, NS or SOA for the special record lookup types");
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+static const GOptionEntry option_entries[] = {
+  { "synchronous", 's', 0, G_OPTION_ARG_NONE, &synchronous, "Synchronous connections", NULL },
+  { "connectable", 'c', 0, G_OPTION_ARG_INT, &connectable_count, "Connectable count", "C" },
+  { "special-type", 't', 0, G_OPTION_ARG_CALLBACK, record_type_arg, "Record type like MX, TXT, NS or SOA", "RR" },
+  { NULL },
+};
+
 int
 main (int argc, char **argv)
 {
-  gboolean synchronous = FALSE;
-  guint connectable_count = 0;
+  GOptionContext *context;
+  GError *error = NULL;
 #ifdef G_OS_UNIX
   GIOChannel *chan;
   guint watch;
@@ -440,22 +686,13 @@ main (int argc, char **argv)
 
   g_type_init ();
 
-  /* FIXME: use GOptionContext */
-  while (argc >= 2 && argv[1][0] == '-')
+  context = g_option_context_new ("lookups ...");
+  g_option_context_add_main_entries (context, option_entries, NULL);
+  if (!g_option_context_parse (context, &argc, &argv, &error))
     {
-      if (!strcmp (argv[1], "-s"))
-        synchronous = TRUE;
-      else if (!strcmp (argv[1], "-c"))
-        {
-          connectable_count = atoi (argv[2]);
-          argv++;
-          argc--;
-        }
-      else
-        usage ();
-
-      argv++;
-      argc--;
+      g_printerr ("%s\n", error->message);
+      g_error_free (error);
+      usage();
     }
 
   if (argc < 2 || (argc > 2 && connectable_count))



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]