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