/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2008 Red Hat, Inc. * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see . */ #include "config.h" #include #include "glibintl.h" #include #include #include #include #ifdef G_OS_UNIX #include #endif #include 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 gint timeout_ms = 0; static G_NORETURN void usage (void) { fprintf (stderr, "Usage: resolver [-s] [hostname | IP | service/protocol/domain ] ...\n"); fprintf (stderr, "Usage: resolver [-s] [-t MX|TXT|NS|SOA|SRV] 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, SOA or SRV to look up DNS records of those types.\n"); exit (1); } G_LOCK_DEFINE_STATIC (response); static void done_lookup (void) { nlookups--; if (nlookups == 0) { /* In the sync case we need to make sure we don't call * g_main_loop_quit before the loop is actually running... */ g_idle_add ((GSourceFunc)g_main_loop_quit, loop); } } static void print_resolved_name (const char *phys, char *name, GError *error) { G_LOCK (response); printf ("Address: %s\n", phys); if (error) { printf ("Error: %s\n", error->message); g_error_free (error); } else { printf ("Name: %s\n", name); g_free (name); } printf ("\n"); done_lookup (); G_UNLOCK (response); } static void print_resolved_addresses (const char *name, GList *addresses, GError *error) { char *phys; GList *a; G_LOCK (response); printf ("Name: %s\n", name); if (error) { printf ("Error: %s\n", error->message); g_error_free (error); } else { for (a = addresses; a; a = a->next) { phys = g_inet_address_to_string (a->data); printf ("Address: %s\n", phys); g_free (phys); g_object_unref (a->data); } g_list_free (addresses); } printf ("\n"); done_lookup (); G_UNLOCK (response); } static void print_resolved_service (const char *service, GList *targets, GError *error) { GList *t; G_LOCK (response); printf ("Service: %s\n", service); if (error) { printf ("Error: %s\n", error->message); g_error_free (error); } else { for (t = targets; t; t = t->next) { printf ("%s:%u (pri %u, weight %u)\n", g_srv_target_get_hostname (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); } printf ("\n"); done_lookup (); G_UNLOCK (response); } 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_free (contents); } g_list_free (records); } printf ("\n"); done_lookup (); G_UNLOCK (response); } static void print_resolved_srv (const char *rrname, GList *records, GError *error) { 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 SRV records\n"); } else { GList *t; for (t = records; t != NULL; t = t->next) { guint16 priority, weight, port; const gchar *target; g_variant_get (t->data, "(qqq&s)", &priority, &weight, &port, &target); printf ("%s (priority %u, weight %u, port %u)\n", target, (guint) priority, (guint) weight, (guint) port); 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 (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; case G_RESOLVER_RECORD_SRV: print_resolved_srv (arg, records, error); break; default: g_warn_if_reached (); break; } } else if (strchr (arg, '/')) { GList *targets; /* service/protocol/domain */ char **parts = g_strsplit (arg, "/", 3); if (!parts || !parts[2]) usage (); targets = g_resolver_lookup_service (resolver, parts[0], parts[1], parts[2], cancellable, &error); print_resolved_service (arg, targets, error); } else if (g_hostname_is_ip_address (arg)) { GInetAddress *addr = g_inet_address_new_from_string (arg); char *name; name = g_resolver_lookup_by_address (resolver, addr, cancellable, &error); print_resolved_name (arg, name, error); g_object_unref (addr); } else { GList *addresses; addresses = g_resolver_lookup_by_name (resolver, arg, cancellable, &error); print_resolved_addresses (arg, addresses, error); } } static gpointer lookup_thread (gpointer arg) { lookup_one_sync (arg); return NULL; } static void start_sync_lookups (char **argv, int argc) { int i; for (i = 0; i < argc; i++) { GThread *thread; thread = g_thread_new ("lookup", lookup_thread, argv[i]); g_thread_unref (thread); } } static void lookup_by_addr_callback (GObject *source, GAsyncResult *result, gpointer user_data) { const char *phys = user_data; GError *error = NULL; char *name; name = g_resolver_lookup_by_address_finish (resolver, result, &error); print_resolved_name (phys, name, error); } static void lookup_by_name_callback (GObject *source, GAsyncResult *result, gpointer user_data) { const char *name = user_data; GError *error = NULL; GList *addresses; addresses = g_resolver_lookup_by_name_finish (resolver, result, &error); print_resolved_addresses (name, addresses, error); } static void lookup_service_callback (GObject *source, GAsyncResult *result, gpointer user_data) { const char *service = user_data; GError *error = NULL; GList *targets; targets = g_resolver_lookup_service_finish (resolver, result, &error); print_resolved_service (service, targets, error); } 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; case G_RESOLVER_RECORD_SRV: print_resolved_srv (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 (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); if (!parts || !parts[2]) usage (); g_resolver_lookup_service_async (resolver, parts[0], parts[1], parts[2], cancellable, lookup_service_callback, argv[i]); } else if (g_hostname_is_ip_address (argv[i])) { GInetAddress *addr = g_inet_address_new_from_string (argv[i]); g_resolver_lookup_by_address_async (resolver, addr, cancellable, lookup_by_addr_callback, argv[i]); g_object_unref (addr); } else { g_resolver_lookup_by_name_async (resolver, argv[i], cancellable, lookup_by_name_callback, argv[i]); } /* Stress-test the reloading code */ g_signal_emit_by_name (resolver, "reload"); } } static void print_connectable_sockaddr (GSocketAddress *sockaddr, GError *error) { char *phys; if (error) { printf ("Error: %s\n", error->message); g_error_free (error); } else if (!G_IS_INET_SOCKET_ADDRESS (sockaddr)) { printf ("Error: Unexpected sockaddr type '%s'\n", g_type_name_from_instance ((GTypeInstance *)sockaddr)); g_object_unref (sockaddr); } else { GInetSocketAddress *isa = G_INET_SOCKET_ADDRESS (sockaddr); phys = g_inet_address_to_string (g_inet_socket_address_get_address (isa)); printf ("Address: %s%s%s:%d\n", strchr (phys, ':') ? "[" : "", phys, strchr (phys, ':') ? "]" : "", g_inet_socket_address_get_port (isa)); g_free (phys); g_object_unref (sockaddr); } } static void do_sync_connectable (GSocketAddressEnumerator *enumerator) { GSocketAddress *sockaddr; GError *error = NULL; while ((sockaddr = g_socket_address_enumerator_next (enumerator, cancellable, &error))) print_connectable_sockaddr (sockaddr, error); g_object_unref (enumerator); done_lookup (); } static void do_async_connectable (GSocketAddressEnumerator *enumerator); static void got_next_async (GObject *source, GAsyncResult *result, gpointer user_data) { GSocketAddressEnumerator *enumerator = G_SOCKET_ADDRESS_ENUMERATOR (source); GSocketAddress *sockaddr; GError *error = NULL; sockaddr = g_socket_address_enumerator_next_finish (enumerator, result, &error); if (sockaddr || error) print_connectable_sockaddr (sockaddr, error); if (sockaddr) do_async_connectable (enumerator); else { g_object_unref (enumerator); done_lookup (); } } static void do_async_connectable (GSocketAddressEnumerator *enumerator) { g_socket_address_enumerator_next_async (enumerator, cancellable, got_next_async, NULL); } static void do_connectable (const char *arg, gboolean synch, guint count) { char **parts; GSocketConnectable *connectable; GSocketAddressEnumerator *enumerator; if (strchr (arg, '/')) { /* service/protocol/domain */ parts = g_strsplit (arg, "/", 3); if (!parts || !parts[2]) usage (); connectable = g_network_service_new (parts[0], parts[1], parts[2]); } else { guint16 port; parts = g_strsplit (arg, ":", 2); if (parts && parts[1]) { arg = parts[0]; port = strtoul (parts[1], NULL, 10); } else port = 0; if (g_hostname_is_ip_address (arg)) { GInetAddress *addr = g_inet_address_new_from_string (arg); GSocketAddress *sockaddr = g_inet_socket_address_new (addr, port); g_object_unref (addr); connectable = G_SOCKET_CONNECTABLE (sockaddr); } else connectable = g_network_address_new (arg, port); } while (count--) { enumerator = g_socket_connectable_enumerate (connectable); if (synch) do_sync_connectable (enumerator); else do_async_connectable (enumerator); } g_object_unref (connectable); } #ifdef G_OS_UNIX static int cancel_fds[2]; static void interrupted (int sig) { gssize c; signal (SIGINT, SIG_DFL); c = write (cancel_fds[1], "x", 1); g_assert_cmpint(c, ==, 1); } static gboolean async_cancel (GIOChannel *source, GIOCondition cond, gpointer cancel) { g_cancellable_cancel (cancel); return FALSE; } #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 if (g_ascii_strcasecmp (value, "SRV") == 0) { record_type = G_RESOLVER_RECORD_SRV; } else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Specify MX, TXT, NS, SOA or SRV 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" }, { "timeout", 0, 0, G_OPTION_ARG_INT, &timeout_ms, "Timeout (ms)", "ms" }, G_OPTION_ENTRY_NULL, }; int main (int argc, char **argv) { GOptionContext *context; GError *error = NULL; #ifdef G_OS_UNIX GIOChannel *chan; guint watch; #endif 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)) { g_printerr ("%s\n", error->message); g_error_free (error); usage(); } if (argc < 2 || (argc > 2 && connectable_count)) usage (); resolver = g_resolver_get_default (); if (timeout_ms != 0) g_resolver_set_timeout (resolver, timeout_ms); cancellable = g_cancellable_new (); #ifdef G_OS_UNIX /* Set up cancellation; we want to cancel if the user ^C's the * program, but we can't cancel directly from an interrupt. */ signal (SIGINT, interrupted); if (pipe (cancel_fds) == -1) { perror ("pipe"); exit (1); } chan = g_io_channel_unix_new (cancel_fds[0]); watch = g_io_add_watch (chan, G_IO_IN, async_cancel, cancellable); g_io_channel_unref (chan); #endif nlookups = argc - 1; loop = g_main_loop_new (NULL, TRUE); if (connectable_count) { nlookups = connectable_count; do_connectable (argv[1], synchronous, connectable_count); } else { if (synchronous) start_sync_lookups (argv + 1, argc - 1); else start_async_lookups (argv + 1, argc - 1); } g_main_loop_run (loop); g_main_loop_unref (loop); #ifdef G_OS_UNIX g_source_remove (watch); #endif g_object_unref (cancellable); g_option_context_free (context); return 0; }