From 666374c16f3d8118fe3422839d22ca32af69e4d0 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Wed, 4 Apr 2012 17:13:10 +0200 Subject: [PATCH] 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 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
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)) -- 2.7.4