/* -*- 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;
}