1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /* GIO - GLib Input, Output and Streaming Library
4  *
5  * Copyright (C) 2008 Red Hat, Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General
18  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 #include <glib.h>
23 #include "glibintl.h"
24 
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #ifdef G_OS_UNIX
30 #include <unistd.h>
31 #endif
32 
33 #include <gio/gio.h>
34 
35 static GResolver *resolver;
36 static GCancellable *cancellable;
37 static GMainLoop *loop;
38 static int nlookups = 0;
39 static gboolean synchronous = FALSE;
40 static guint connectable_count = 0;
41 static GResolverRecordType record_type = 0;
42 
43 static G_NORETURN void
usage(void)44 usage (void)
45 {
46 	fprintf (stderr, "Usage: resolver [-s] [hostname | IP | service/protocol/domain ] ...\n");
47 	fprintf (stderr, "Usage: resolver [-s] [-t MX|TXT|NS|SOA] rrname ...\n");
48 	fprintf (stderr, "       resolver [-s] -c NUMBER [hostname | IP | service/protocol/domain ]\n");
49 	fprintf (stderr, "       Use -s to do synchronous lookups.\n");
50 	fprintf (stderr, "       Use -c NUMBER (and only a single resolvable argument) to test GSocketConnectable.\n");
51 	fprintf (stderr, "       The given NUMBER determines how many times the connectable will be enumerated.\n");
52 	fprintf (stderr, "       Use -t with MX, TXT, NS or SOA to look up DNS records of those types.\n");
53 	exit (1);
54 }
55 
56 G_LOCK_DEFINE_STATIC (response);
57 
58 static void
done_lookup(void)59 done_lookup (void)
60 {
61   nlookups--;
62   if (nlookups == 0)
63     {
64       /* In the sync case we need to make sure we don't call
65        * g_main_loop_quit before the loop is actually running...
66        */
67       g_idle_add ((GSourceFunc)g_main_loop_quit, loop);
68     }
69 }
70 
71 static void
print_resolved_name(const char * phys,char * name,GError * error)72 print_resolved_name (const char *phys,
73                      char       *name,
74                      GError     *error)
75 {
76   G_LOCK (response);
77   printf ("Address: %s\n", phys);
78   if (error)
79     {
80       printf ("Error:   %s\n", error->message);
81       g_error_free (error);
82     }
83   else
84     {
85       printf ("Name:    %s\n", name);
86       g_free (name);
87     }
88   printf ("\n");
89 
90   done_lookup ();
91   G_UNLOCK (response);
92 }
93 
94 static void
print_resolved_addresses(const char * name,GList * addresses,GError * error)95 print_resolved_addresses (const char *name,
96                           GList      *addresses,
97 			  GError     *error)
98 {
99   char *phys;
100   GList *a;
101 
102   G_LOCK (response);
103   printf ("Name:    %s\n", name);
104   if (error)
105     {
106       printf ("Error:   %s\n", error->message);
107       g_error_free (error);
108     }
109   else
110     {
111       for (a = addresses; a; a = a->next)
112 	{
113 	  phys = g_inet_address_to_string (a->data);
114 	  printf ("Address: %s\n", phys);
115 	  g_free (phys);
116           g_object_unref (a->data);
117 	}
118       g_list_free (addresses);
119     }
120   printf ("\n");
121 
122   done_lookup ();
123   G_UNLOCK (response);
124 }
125 
126 static void
print_resolved_service(const char * service,GList * targets,GError * error)127 print_resolved_service (const char *service,
128                         GList      *targets,
129 			GError     *error)
130 {
131   GList *t;
132 
133   G_LOCK (response);
134   printf ("Service: %s\n", service);
135   if (error)
136     {
137       printf ("Error: %s\n", error->message);
138       g_error_free (error);
139     }
140   else
141     {
142       for (t = targets; t; t = t->next)
143 	{
144 	  printf ("%s:%u (pri %u, weight %u)\n",
145 		  g_srv_target_get_hostname (t->data),
146 		  (guint)g_srv_target_get_port (t->data),
147 		  (guint)g_srv_target_get_priority (t->data),
148 		  (guint)g_srv_target_get_weight (t->data));
149           g_srv_target_free (t->data);
150 	}
151       g_list_free (targets);
152     }
153   printf ("\n");
154 
155   done_lookup ();
156   G_UNLOCK (response);
157 }
158 
159 static void
print_resolved_mx(const char * rrname,GList * records,GError * error)160 print_resolved_mx (const char *rrname,
161                    GList      *records,
162                    GError     *error)
163 {
164   const gchar *hostname;
165   guint16 priority;
166   GList *t;
167 
168   G_LOCK (response);
169   printf ("Domain: %s\n", rrname);
170   if (error)
171     {
172       printf ("Error: %s\n", error->message);
173       g_error_free (error);
174     }
175   else if (!records)
176     {
177       printf ("no MX records\n");
178     }
179   else
180     {
181       for (t = records; t; t = t->next)
182         {
183           g_variant_get (t->data, "(q&s)", &priority, &hostname);
184           printf ("%s (pri %u)\n", hostname, (guint)priority);
185           g_variant_unref (t->data);
186         }
187       g_list_free (records);
188     }
189   printf ("\n");
190 
191   done_lookup ();
192   G_UNLOCK (response);
193 }
194 
195 static void
print_resolved_txt(const char * rrname,GList * records,GError * error)196 print_resolved_txt (const char *rrname,
197                     GList      *records,
198                     GError     *error)
199 {
200   const gchar **contents;
201   GList *t;
202   gint i;
203 
204   G_LOCK (response);
205   printf ("Domain: %s\n", rrname);
206   if (error)
207     {
208       printf ("Error: %s\n", error->message);
209       g_error_free (error);
210     }
211   else if (!records)
212     {
213       printf ("no TXT records\n");
214     }
215   else
216     {
217       for (t = records; t; t = t->next)
218         {
219           if (t != records)
220             printf ("\n");
221           g_variant_get (t->data, "(^a&s)", &contents);
222           for (i = 0; contents[i] != NULL; i++)
223             printf ("%s\n", contents[i]);
224           g_variant_unref (t->data);
225           g_free (contents);
226         }
227       g_list_free (records);
228     }
229   printf ("\n");
230 
231   done_lookup ();
232   G_UNLOCK (response);
233 }
234 
235 static void
print_resolved_soa(const char * rrname,GList * records,GError * error)236 print_resolved_soa (const char *rrname,
237                     GList      *records,
238                     GError     *error)
239 {
240   GList *t;
241   const gchar *primary_ns;
242   const gchar *administrator;
243   guint32 serial, refresh, retry, expire, ttl;
244 
245   G_LOCK (response);
246   printf ("Zone: %s\n", rrname);
247   if (error)
248     {
249       printf ("Error: %s\n", error->message);
250       g_error_free (error);
251     }
252   else if (!records)
253     {
254       printf ("no SOA records\n");
255     }
256   else
257     {
258       for (t = records; t; t = t->next)
259         {
260           g_variant_get (t->data, "(&s&suuuuu)", &primary_ns, &administrator,
261                          &serial, &refresh, &retry, &expire, &ttl);
262           printf ("%s %s (serial %u, refresh %u, retry %u, expire %u, ttl %u)\n",
263                   primary_ns, administrator, (guint)serial, (guint)refresh,
264                   (guint)retry, (guint)expire, (guint)ttl);
265           g_variant_unref (t->data);
266         }
267       g_list_free (records);
268     }
269   printf ("\n");
270 
271   done_lookup ();
272   G_UNLOCK (response);
273 }
274 
275 static void
print_resolved_ns(const char * rrname,GList * records,GError * error)276 print_resolved_ns (const char *rrname,
277                     GList      *records,
278                     GError     *error)
279 {
280   GList *t;
281   const gchar *hostname;
282 
283   G_LOCK (response);
284   printf ("Zone: %s\n", rrname);
285   if (error)
286     {
287       printf ("Error: %s\n", error->message);
288       g_error_free (error);
289     }
290   else if (!records)
291     {
292       printf ("no NS records\n");
293     }
294   else
295     {
296       for (t = records; t; t = t->next)
297         {
298           g_variant_get (t->data, "(&s)", &hostname);
299           printf ("%s\n", hostname);
300           g_variant_unref (t->data);
301         }
302       g_list_free (records);
303     }
304   printf ("\n");
305 
306   done_lookup ();
307   G_UNLOCK (response);
308 }
309 
310 static void
lookup_one_sync(const char * arg)311 lookup_one_sync (const char *arg)
312 {
313   GError *error = NULL;
314 
315   if (record_type != 0)
316     {
317       GList *records;
318 
319       records = g_resolver_lookup_records (resolver, arg, record_type, cancellable, &error);
320       switch (record_type)
321       {
322         case G_RESOLVER_RECORD_MX:
323           print_resolved_mx (arg, records, error);
324           break;
325         case G_RESOLVER_RECORD_SOA:
326           print_resolved_soa (arg, records, error);
327           break;
328         case G_RESOLVER_RECORD_NS:
329           print_resolved_ns (arg, records, error);
330           break;
331         case G_RESOLVER_RECORD_TXT:
332           print_resolved_txt (arg, records, error);
333           break;
334         default:
335           g_warn_if_reached ();
336           break;
337       }
338     }
339   else if (strchr (arg, '/'))
340     {
341       GList *targets;
342       /* service/protocol/domain */
343       char **parts = g_strsplit (arg, "/", 3);
344 
345       if (!parts || !parts[2])
346 	usage ();
347 
348       targets = g_resolver_lookup_service (resolver,
349                                            parts[0], parts[1], parts[2],
350                                            cancellable, &error);
351       print_resolved_service (arg, targets, error);
352     }
353   else if (g_hostname_is_ip_address (arg))
354     {
355       GInetAddress *addr = g_inet_address_new_from_string (arg);
356       char *name;
357 
358       name = g_resolver_lookup_by_address (resolver, addr, cancellable, &error);
359       print_resolved_name (arg, name, error);
360       g_object_unref (addr);
361     }
362   else
363     {
364       GList *addresses;
365 
366       addresses = g_resolver_lookup_by_name (resolver, arg, cancellable, &error);
367       print_resolved_addresses (arg, addresses, error);
368     }
369 }
370 
371 static gpointer
lookup_thread(gpointer arg)372 lookup_thread (gpointer arg)
373 {
374   lookup_one_sync (arg);
375   return NULL;
376 }
377 
378 static void
start_sync_lookups(char ** argv,int argc)379 start_sync_lookups (char **argv, int argc)
380 {
381   int i;
382 
383   for (i = 0; i < argc; i++)
384     {
385       GThread *thread;
386       thread = g_thread_new ("lookup", lookup_thread, argv[i]);
387       g_thread_unref (thread);
388     }
389 }
390 
391 static void
lookup_by_addr_callback(GObject * source,GAsyncResult * result,gpointer user_data)392 lookup_by_addr_callback (GObject *source, GAsyncResult *result,
393                          gpointer user_data)
394 {
395   const char *phys = user_data;
396   GError *error = NULL;
397   char *name;
398 
399   name = g_resolver_lookup_by_address_finish (resolver, result, &error);
400   print_resolved_name (phys, name, error);
401 }
402 
403 static void
lookup_by_name_callback(GObject * source,GAsyncResult * result,gpointer user_data)404 lookup_by_name_callback (GObject *source, GAsyncResult *result,
405                          gpointer user_data)
406 {
407   const char *name = user_data;
408   GError *error = NULL;
409   GList *addresses;
410 
411   addresses = g_resolver_lookup_by_name_finish (resolver, result, &error);
412   print_resolved_addresses (name, addresses, error);
413 }
414 
415 static void
lookup_service_callback(GObject * source,GAsyncResult * result,gpointer user_data)416 lookup_service_callback (GObject *source, GAsyncResult *result,
417 			 gpointer user_data)
418 {
419   const char *service = user_data;
420   GError *error = NULL;
421   GList *targets;
422 
423   targets = g_resolver_lookup_service_finish (resolver, result, &error);
424   print_resolved_service (service, targets, error);
425 }
426 
427 static void
lookup_records_callback(GObject * source,GAsyncResult * result,gpointer user_data)428 lookup_records_callback (GObject      *source,
429                          GAsyncResult *result,
430                          gpointer      user_data)
431 {
432   const char *arg = user_data;
433   GError *error = NULL;
434   GList *records;
435 
436   records = g_resolver_lookup_records_finish (resolver, result, &error);
437 
438   switch (record_type)
439   {
440     case G_RESOLVER_RECORD_MX:
441       print_resolved_mx (arg, records, error);
442       break;
443     case G_RESOLVER_RECORD_SOA:
444       print_resolved_soa (arg, records, error);
445       break;
446     case G_RESOLVER_RECORD_NS:
447       print_resolved_ns (arg, records, error);
448       break;
449     case G_RESOLVER_RECORD_TXT:
450       print_resolved_txt (arg, records, error);
451       break;
452     default:
453       g_warn_if_reached ();
454       break;
455   }
456 }
457 
458 static void
start_async_lookups(char ** argv,int argc)459 start_async_lookups (char **argv, int argc)
460 {
461   int i;
462 
463   for (i = 0; i < argc; i++)
464     {
465       if (record_type != 0)
466 	{
467 	  g_resolver_lookup_records_async (resolver, argv[i], record_type,
468 	                                   cancellable, lookup_records_callback, argv[i]);
469 	}
470       else if (strchr (argv[i], '/'))
471 	{
472 	  /* service/protocol/domain */
473 	  char **parts = g_strsplit (argv[i], "/", 3);
474 
475 	  if (!parts || !parts[2])
476 	    usage ();
477 
478 	  g_resolver_lookup_service_async (resolver,
479 					   parts[0], parts[1], parts[2],
480 					   cancellable,
481 					   lookup_service_callback, argv[i]);
482 	}
483       else if (g_hostname_is_ip_address (argv[i]))
484 	{
485           GInetAddress *addr = g_inet_address_new_from_string (argv[i]);
486 
487 	  g_resolver_lookup_by_address_async (resolver, addr, cancellable,
488                                               lookup_by_addr_callback, argv[i]);
489 	  g_object_unref (addr);
490 	}
491       else
492 	{
493 	  g_resolver_lookup_by_name_async (resolver, argv[i], cancellable,
494                                            lookup_by_name_callback,
495                                            argv[i]);
496 	}
497 
498       /* Stress-test the reloading code */
499       g_signal_emit_by_name (resolver, "reload");
500     }
501 }
502 
503 static void
print_connectable_sockaddr(GSocketAddress * sockaddr,GError * error)504 print_connectable_sockaddr (GSocketAddress *sockaddr,
505                             GError         *error)
506 {
507   char *phys;
508 
509   if (error)
510     {
511       printf ("Error:   %s\n", error->message);
512       g_error_free (error);
513     }
514   else if (!G_IS_INET_SOCKET_ADDRESS (sockaddr))
515     {
516       printf ("Error:   Unexpected sockaddr type '%s'\n", g_type_name_from_instance ((GTypeInstance *)sockaddr));
517       g_object_unref (sockaddr);
518     }
519   else
520     {
521       GInetSocketAddress *isa = G_INET_SOCKET_ADDRESS (sockaddr);
522       phys = g_inet_address_to_string (g_inet_socket_address_get_address (isa));
523       printf ("Address: %s%s%s:%d\n",
524               strchr (phys, ':') ? "[" : "", phys, strchr (phys, ':') ? "]" : "",
525               g_inet_socket_address_get_port (isa));
526       g_free (phys);
527       g_object_unref (sockaddr);
528     }
529 }
530 
531 static void
do_sync_connectable(GSocketAddressEnumerator * enumerator)532 do_sync_connectable (GSocketAddressEnumerator *enumerator)
533 {
534   GSocketAddress *sockaddr;
535   GError *error = NULL;
536 
537   while ((sockaddr = g_socket_address_enumerator_next (enumerator, cancellable, &error)))
538     print_connectable_sockaddr (sockaddr, error);
539 
540   g_object_unref (enumerator);
541   done_lookup ();
542 }
543 
544 static void do_async_connectable (GSocketAddressEnumerator *enumerator);
545 
546 static void
got_next_async(GObject * source,GAsyncResult * result,gpointer user_data)547 got_next_async (GObject *source, GAsyncResult *result, gpointer user_data)
548 {
549   GSocketAddressEnumerator *enumerator = G_SOCKET_ADDRESS_ENUMERATOR (source);
550   GSocketAddress *sockaddr;
551   GError *error = NULL;
552 
553   sockaddr = g_socket_address_enumerator_next_finish (enumerator, result, &error);
554   if (sockaddr || error)
555     print_connectable_sockaddr (sockaddr, error);
556   if (sockaddr)
557     do_async_connectable (enumerator);
558   else
559     {
560       g_object_unref (enumerator);
561       done_lookup ();
562     }
563 }
564 
565 static void
do_async_connectable(GSocketAddressEnumerator * enumerator)566 do_async_connectable (GSocketAddressEnumerator *enumerator)
567 {
568   g_socket_address_enumerator_next_async (enumerator, cancellable,
569                                           got_next_async, NULL);
570 }
571 
572 static void
do_connectable(const char * arg,gboolean synchronous,guint count)573 do_connectable (const char *arg, gboolean synchronous, guint count)
574 {
575   char **parts;
576   GSocketConnectable *connectable;
577   GSocketAddressEnumerator *enumerator;
578 
579   if (strchr (arg, '/'))
580     {
581       /* service/protocol/domain */
582       parts = g_strsplit (arg, "/", 3);
583       if (!parts || !parts[2])
584 	usage ();
585 
586       connectable = g_network_service_new (parts[0], parts[1], parts[2]);
587     }
588   else
589     {
590       guint16 port;
591 
592       parts = g_strsplit (arg, ":", 2);
593       if (parts && parts[1])
594 	{
595 	  arg = parts[0];
596 	  port = strtoul (parts[1], NULL, 10);
597 	}
598       else
599 	port = 0;
600 
601       if (g_hostname_is_ip_address (arg))
602 	{
603 	  GInetAddress *addr = g_inet_address_new_from_string (arg);
604 	  GSocketAddress *sockaddr = g_inet_socket_address_new (addr, port);
605 
606 	  g_object_unref (addr);
607 	  connectable = G_SOCKET_CONNECTABLE (sockaddr);
608 	}
609       else
610         connectable = g_network_address_new (arg, port);
611     }
612 
613   while (count--)
614     {
615       enumerator = g_socket_connectable_enumerate (connectable);
616 
617       if (synchronous)
618         do_sync_connectable (enumerator);
619       else
620         do_async_connectable (enumerator);
621     }
622 
623   g_object_unref (connectable);
624 }
625 
626 #ifdef G_OS_UNIX
627 static int cancel_fds[2];
628 
629 static void
interrupted(int sig)630 interrupted (int sig)
631 {
632   gssize c;
633 
634   signal (SIGINT, SIG_DFL);
635   c = write (cancel_fds[1], "x", 1);
636   g_assert_cmpint(c, ==, 1);
637 }
638 
639 static gboolean
async_cancel(GIOChannel * source,GIOCondition cond,gpointer cancel)640 async_cancel (GIOChannel *source, GIOCondition cond, gpointer cancel)
641 {
642   g_cancellable_cancel (cancel);
643   return FALSE;
644 }
645 #endif
646 
647 
648 static gboolean
record_type_arg(const gchar * option_name,const gchar * value,gpointer data,GError ** error)649 record_type_arg (const gchar *option_name,
650                  const gchar *value,
651                  gpointer data,
652                  GError **error)
653 {
654   if (g_ascii_strcasecmp (value, "MX") == 0) {
655     record_type = G_RESOLVER_RECORD_MX;
656   } else if (g_ascii_strcasecmp (value, "TXT") == 0) {
657     record_type = G_RESOLVER_RECORD_TXT;
658   } else if (g_ascii_strcasecmp (value, "SOA") == 0) {
659     record_type = G_RESOLVER_RECORD_SOA;
660   } else if (g_ascii_strcasecmp (value, "NS") == 0) {
661     record_type = G_RESOLVER_RECORD_NS;
662   } else {
663       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
664                    "Specify MX, TXT, NS or SOA for the special record lookup types");
665       return FALSE;
666   }
667 
668   return TRUE;
669 }
670 
671 static const GOptionEntry option_entries[] = {
672   { "synchronous", 's', 0, G_OPTION_ARG_NONE, &synchronous, "Synchronous connections", NULL },
673   { "connectable", 'c', 0, G_OPTION_ARG_INT, &connectable_count, "Connectable count", "C" },
674   { "special-type", 't', 0, G_OPTION_ARG_CALLBACK, record_type_arg, "Record type like MX, TXT, NS or SOA", "RR" },
675   G_OPTION_ENTRY_NULL,
676 };
677 
678 int
main(int argc,char ** argv)679 main (int argc, char **argv)
680 {
681   GOptionContext *context;
682   GError *error = NULL;
683 #ifdef G_OS_UNIX
684   GIOChannel *chan;
685   guint watch;
686 #endif
687 
688   context = g_option_context_new ("lookups ...");
689   g_option_context_add_main_entries (context, option_entries, NULL);
690   if (!g_option_context_parse (context, &argc, &argv, &error))
691     {
692       g_printerr ("%s\n", error->message);
693       g_error_free (error);
694       usage();
695     }
696 
697   if (argc < 2 || (argc > 2 && connectable_count))
698     usage ();
699 
700   resolver = g_resolver_get_default ();
701 
702   cancellable = g_cancellable_new ();
703 
704 #ifdef G_OS_UNIX
705   /* Set up cancellation; we want to cancel if the user ^C's the
706    * program, but we can't cancel directly from an interrupt.
707    */
708   signal (SIGINT, interrupted);
709 
710   if (pipe (cancel_fds) == -1)
711     {
712       perror ("pipe");
713       exit (1);
714     }
715   chan = g_io_channel_unix_new (cancel_fds[0]);
716   watch = g_io_add_watch (chan, G_IO_IN, async_cancel, cancellable);
717   g_io_channel_unref (chan);
718 #endif
719 
720   nlookups = argc - 1;
721   loop = g_main_loop_new (NULL, TRUE);
722 
723   if (connectable_count)
724     {
725       nlookups = connectable_count;
726       do_connectable (argv[1], synchronous, connectable_count);
727     }
728   else
729     {
730       if (synchronous)
731         start_sync_lookups (argv + 1, argc - 1);
732       else
733         start_async_lookups (argv + 1, argc - 1);
734     }
735 
736   g_main_loop_run (loop);
737   g_main_loop_unref (loop);
738 
739 #ifdef G_OS_UNIX
740   g_source_remove (watch);
741 #endif
742   g_object_unref (cancellable);
743   g_option_context_free (context);
744 
745   return 0;
746 }
747