chiark / gitweb /
resolve-host: make arg_type an int
[elogind.git] / src / resolve-host / resolve-host.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Zbigniew JÄ™drzejewski-Szmek
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <arpa/inet.h>
23 #include <net/if.h>
24 #include <getopt.h>
25
26 #include "sd-bus.h"
27 #include "bus-util.h"
28 #include "bus-error.h"
29 #include "bus-errors.h"
30 #include "in-addr-util.h"
31 #include "af-list.h"
32 #include "build.h"
33
34 #include "resolved-dns-packet.h"
35
36 #define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
37
38 static int arg_family = AF_UNSPEC;
39 static int arg_ifindex = 0;
40 static int arg_type = 0;
41 static uint16_t arg_class = 0;
42 static bool arg_legend = true;
43
44 static int resolve_host(sd_bus *bus, const char *name) {
45
46         _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
47         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
48         const char *canonical = NULL;
49         unsigned c = 0;
50         int r;
51
52         assert(name);
53
54         log_debug("Resolving %s (family %s, ifindex %i).", name, af_to_name(arg_family) ?: "*", arg_ifindex);
55
56         r = sd_bus_message_new_method_call(
57                         bus,
58                         &req,
59                         "org.freedesktop.resolve1",
60                         "/org/freedesktop/resolve1",
61                         "org.freedesktop.resolve1.Manager",
62                         "ResolveHostname");
63         if (r < 0)
64                 return bus_log_create_error(r);
65
66         r = sd_bus_message_set_auto_start(req, false);
67         if (r < 0)
68                 return bus_log_create_error(r);
69
70         r = sd_bus_message_append(req, "si", name, arg_family);
71         if (r < 0)
72                 return bus_log_create_error(r);
73
74         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
75         if (r < 0) {
76                 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
77                 return r;
78         }
79
80         r = sd_bus_message_enter_container(reply, 'a', "(iayi)");
81         if (r < 0)
82                 return bus_log_parse_error(r);
83
84         while ((r = sd_bus_message_enter_container(reply, 'r', "iayi")) > 0) {
85                 const void *a;
86                 int family, ifindex;
87                 size_t sz;
88                 _cleanup_free_ char *pretty = NULL;
89                 char ifname[IF_NAMESIZE] = "";
90
91                 r = sd_bus_message_read(reply, "i", &family);
92                 if (r < 0)
93                         return bus_log_parse_error(r);
94
95                 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
96                 if (r < 0)
97                         return bus_log_parse_error(r);
98
99                 r = sd_bus_message_read(reply, "i", &ifindex);
100                 if (r < 0)
101                         return bus_log_parse_error(r);
102
103                 r = sd_bus_message_exit_container(reply);
104                 if (r < 0)
105                         return bus_log_parse_error(r);
106
107                 if (!IN_SET(family, AF_INET, AF_INET6)) {
108                         log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown");
109                         continue;
110                 }
111
112                 if (sz != FAMILY_ADDRESS_SIZE(family)) {
113                         log_error("%s: systemd-resolved returned address of invalid size %zu for family %s",
114                                   name, sz, af_to_name(family) ?: "unknown");
115                         continue;
116                 }
117
118                 if (ifindex < 0) {
119                         log_error("%s: systemd-resolved returned invalid interface index %i",
120                                   name, ifindex);
121                         continue;
122                 }
123
124                 if (ifindex > 0) {
125                         char *t;
126
127                         t = if_indextoname(ifindex, ifname);
128                         if (!t) {
129                                 log_error("Failed to resolve interface name for index %i", ifindex);
130                                 continue;
131                         }
132                 }
133
134                 if (arg_ifindex > 0 && ifindex > 0 && ifindex != arg_ifindex) {
135                         log_debug("%s: skipping entry with ifindex %i (%s)",
136                                   name, ifindex, ifname);
137                         continue;
138                 }
139
140                 r = in_addr_to_string(family, a, &pretty);
141                 if (r < 0) {
142                         log_error("%s: failed to print address: %s", name, strerror(-r));
143                         continue;
144                 }
145
146                 printf("%*s%s %s%s%s\n",
147                        (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
148                        pretty,
149                        isempty(ifname) ? "" : "%", ifname);
150
151                 c++;
152         }
153         if (r < 0)
154                 return bus_log_parse_error(r);
155
156         r = sd_bus_message_exit_container(reply);
157         if (r < 0)
158                 return bus_log_parse_error(r);
159
160         r = sd_bus_message_read(reply, "s", &canonical);
161         if (r < 0)
162                 return bus_log_parse_error(r);
163
164         if (!streq(name, canonical)) {
165                 printf("%*s%s (%s)\n",
166                        (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
167                        canonical);
168         }
169
170         if (c == 0) {
171                 log_error("%s: no addresses found", name);
172                 return -ESRCH;
173         }
174
175         return 0;
176 }
177
178 static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) {
179         _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
180         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
181         _cleanup_free_ char *pretty = NULL;
182         char ifname[IF_NAMESIZE] = "";
183         unsigned c = 0;
184         const char *n;
185         int r;
186
187         assert(bus);
188         assert(IN_SET(family, AF_INET, AF_INET6));
189         assert(address);
190
191         r = in_addr_to_string(family, address, &pretty);
192         if (r < 0)
193                 return log_oom();
194
195         if (ifindex > 0) {
196                 char *t;
197
198                 t = if_indextoname(ifindex, ifname);
199                 if (!t) {
200                         log_error("Failed to resolve interface name for index %i", ifindex);
201                         return -errno;
202                 }
203         }
204
205         log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname);
206
207         r = sd_bus_message_new_method_call(
208                         bus,
209                         &req,
210                         "org.freedesktop.resolve1",
211                         "/org/freedesktop/resolve1",
212                         "org.freedesktop.resolve1.Manager",
213                         "ResolveAddress");
214         if (r < 0)
215                 return bus_log_create_error(r);
216
217         r = sd_bus_message_set_auto_start(req, false);
218         if (r < 0)
219                 return bus_log_create_error(r);
220
221         r = sd_bus_message_append(req, "i", family);
222         if (r < 0)
223                 return bus_log_create_error(r);
224
225         r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family));
226         if (r < 0)
227                 return bus_log_create_error(r);
228
229         r = sd_bus_message_append(req, "i", ifindex);
230         if (r < 0)
231                 return bus_log_create_error(r);
232
233         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
234         if (r < 0) {
235                 log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r));
236                 return r;
237         }
238
239         r = sd_bus_message_enter_container(reply, 'a', "s");
240         if (r < 0)
241                 return bus_log_create_error(r);
242
243         while ((r = sd_bus_message_read(reply, "s", &n)) > 0) {
244
245                 printf("%*s%s%s%s %s\n",
246                        (int) strlen(pretty), c == 0 ? pretty : "",
247                        isempty(ifname) ? "" : "%", ifname,
248                        c == 0 ? ":" : " ",
249                        n);
250
251                 c++;
252         }
253         if (r < 0)
254                 return bus_log_parse_error(r);
255
256         r = sd_bus_message_exit_container(reply);
257         if (r < 0)
258                 return bus_log_parse_error(r);
259
260         if (c == 0) {
261                 log_error("%s: no names found", pretty);
262                 return -ESRCH;
263         }
264
265         return 0;
266 }
267
268 static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) {
269         const char *percent, *a;
270         int ifi = 0;
271         int r;
272
273         percent = strchr(s, '%');
274         if (percent) {
275                 r = safe_atoi(percent+1, &ifi);
276                 if (r < 0 || ifi <= 0) {
277                         ifi = if_nametoindex(percent+1);
278                         if (ifi <= 0)
279                                 return -EINVAL;
280                 }
281
282                 a = strndupa(s, percent - s);
283         } else
284                 a = s;
285
286         r = in_addr_from_string_auto(a, family, address);
287         if (r < 0)
288                 return r;
289
290         *ifindex = ifi;
291         return 0;
292 }
293
294 static int resolve_record(sd_bus *bus, const char *name) {
295
296         _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
297         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
298         unsigned n = 0;
299         int r;
300
301         assert(name);
302
303         log_debug("Resolving %s %s %s.", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type));
304
305         r = sd_bus_message_new_method_call(
306                         bus,
307                         &req,
308                         "org.freedesktop.resolve1",
309                         "/org/freedesktop/resolve1",
310                         "org.freedesktop.resolve1.Manager",
311                         "ResolveRecord");
312         if (r < 0)
313                 return bus_log_create_error(r);
314
315         r = sd_bus_message_set_auto_start(req, false);
316         if (r < 0)
317                 return bus_log_create_error(r);
318
319         assert((uint16_t) arg_type == arg_type);
320         r = sd_bus_message_append(req, "sqq", name, arg_class, arg_type);
321         if (r < 0)
322                 return bus_log_create_error(r);
323
324         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
325         if (r < 0) {
326                 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
327                 return r;
328         }
329
330         r = sd_bus_message_enter_container(reply, 'a', "(qqay)");
331         if (r < 0)
332                 return bus_log_parse_error(r);
333
334         while ((r = sd_bus_message_enter_container(reply, 'r', "qqay")) > 0) {
335                 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
336                 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
337                 _cleanup_free_ char *s = NULL;
338                 uint16_t c, t;
339                 const void *d;
340                 size_t l;
341
342                 r = sd_bus_message_read(reply, "qq", &c, &t);
343                 if (r < 0)
344                         return bus_log_parse_error(r);
345
346                 r = sd_bus_message_read_array(reply, 'y', &d, &l);
347                 if (r < 0)
348                         return bus_log_parse_error(r);
349
350                 r = sd_bus_message_exit_container(reply);
351                 if (r < 0)
352                         return bus_log_parse_error(r);
353
354                 r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
355                 if (r < 0)
356                         return log_oom();
357
358                 r = dns_packet_append_blob(p, d, l, NULL);
359                 if (r < 0)
360                         return log_oom();
361
362                 r = dns_packet_read_rr(p, &rr, NULL);
363                 if (r < 0) {
364                         log_error("Failed to parse RR.");
365                         return r;
366                 }
367
368                 r = dns_resource_record_to_string(rr, &s);
369                 if (r < 0) {
370                         log_error("Failed to format RR.");
371                         return r;
372                 }
373
374                 printf("%s\n", s);
375                 n++;
376         }
377         if (r < 0)
378                 return bus_log_parse_error(r);
379
380         r = sd_bus_message_exit_container(reply);
381         if (r < 0)
382                 return bus_log_parse_error(r);
383
384         if (n == 0) {
385                 log_error("%s: no records found", name);
386                 return -ESRCH;
387         }
388
389         return 0;
390 }
391
392 static void help_dns_types(void) {
393         int i;
394         const char *t;
395
396         if (arg_legend)
397                 puts("Known dns types:");
398         for (i = 0; i < _DNS_TYPE_MAX; i++) {
399                 t = dns_type_to_string(i);
400                 if (t)
401                         puts(t);
402         }
403 }
404
405 static void help_dns_classes(void) {
406         int i;
407         const char *t;
408
409         if (arg_legend)
410                 puts("Known dns classes:");
411         for (i = 0; i < _DNS_CLASS_MAX; i++) {
412                 t = dns_class_to_string(i);
413                 if (t)
414                         puts(t);
415         }
416 }
417
418 static void help(void) {
419         printf("%s [OPTIONS...]\n\n"
420                "Resolve IPv4 or IPv6 addresses.\n\n"
421                "  -h --help             Show this help\n"
422                "     --version          Show package version\n"
423                "  -4                    Resolve IPv4 addresses\n"
424                "  -6                    Resolve IPv6 addresses\n"
425                "  -i INTERFACE          Filter by interface\n"
426                "  -t --type=TYPE        Query RR with DNS type\n"
427                "  -c --class=CLASS      Query RR with DNS class\n"
428                "  --no-legend           Do not print column headers\n"
429                , program_invocation_short_name);
430 }
431
432 static int parse_argv(int argc, char *argv[]) {
433         enum {
434                 ARG_VERSION = 0x100,
435                 ARG_NO_LEGEND,
436         };
437
438         static const struct option options[] = {
439                 { "help",        no_argument,       NULL, 'h'           },
440                 { "version",     no_argument,       NULL, ARG_VERSION   },
441                 { "type",        no_argument,       NULL, 't'           },
442                 { "class",       no_argument,       NULL, 'c'           },
443                 { "no-legend",   no_argument,       NULL, ARG_NO_LEGEND },
444                 {}
445         };
446
447         int c, r;
448
449         assert(argc >= 0);
450         assert(argv);
451
452         while ((c = getopt_long(argc, argv, "h46i:t:c:", options, NULL)) >= 0)
453                 switch(c) {
454
455                 case 'h':
456                         help();
457                         return 0; /* done */;
458
459                 case ARG_VERSION:
460                         puts(PACKAGE_STRING);
461                         puts(SYSTEMD_FEATURES);
462                         return 0 /* done */;
463
464                 case '4':
465                         arg_family = AF_INET;
466                         break;
467
468                 case '6':
469                         arg_family = AF_INET6;
470                         break;
471
472                 case 'i':
473                         arg_ifindex = if_nametoindex(optarg);
474                         if (arg_ifindex <= 0) {
475                                 log_error("Unknown interfaces %s: %m", optarg);
476                                 return -errno;
477                         }
478                         break;
479
480                 case 't':
481                         if (streq(optarg, "help")) {
482                                 help_dns_types();
483                                 return 0;
484                         }
485
486                         arg_type = dns_type_from_string(optarg);
487                         if (arg_type < 0) {
488                                 log_error("Failed to parse RR record type %s", optarg);
489                                 return r;
490                         }
491                         assert(arg_type > 0 && (uint16_t) arg_type == arg_type);
492
493                         break;
494
495                 case 'c':
496                         if (streq(optarg, "help")) {
497                                 help_dns_classes();
498                                 return 0;
499                         }
500
501                         r = dns_class_from_string(optarg, &arg_class);
502                         if (r < 0) {
503                                 log_error("Failed to parse RR record class %s", optarg);
504                                 return r;
505                         }
506
507                         break;
508
509                 case ARG_NO_LEGEND:
510                         arg_legend = false;
511                         break;
512
513                 case '?':
514                         return -EINVAL;
515
516                 default:
517                         assert_not_reached("Unhandled option");
518                 }
519
520         if (arg_type == 0 && arg_class != 0) {
521                 log_error("--class= may only be used in conjunction with --type=");
522                 return -EINVAL;
523         }
524
525         if (arg_type != 0 && arg_class == 0)
526                 arg_class = DNS_CLASS_IN;
527
528         return 1 /* work to do */;
529 }
530
531 int main(int argc, char **argv) {
532         _cleanup_bus_unref_ sd_bus *bus = NULL;
533         int r;
534
535         log_parse_environment();
536         log_open();
537
538         r = parse_argv(argc, argv);
539         if (r <= 0)
540                 goto finish;
541
542         if (optind >= argc) {
543                 log_error("No arguments passed");
544                 r = -EINVAL;
545                 goto finish;
546         }
547
548         r = sd_bus_open_system(&bus);
549         if (r < 0) {
550                 log_error("sd_bus_open_system: %s", strerror(-r));
551                 goto finish;
552         }
553
554         while (argv[optind]) {
555                 int family, ifindex, k;
556                 union in_addr_union a;
557
558                 if (arg_type != 0)
559                         k = resolve_record(bus, argv[optind]);
560                 else {
561                         k = parse_address(argv[optind], &family, &a, &ifindex);
562                         if (k >= 0)
563                                 k = resolve_address(bus, family, &a, ifindex);
564                         else
565                                 k = resolve_host(bus, argv[optind]);
566                 }
567
568                 if (r == 0)
569                         r = k;
570
571                 optind++;
572         }
573
574 finish:
575         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
576 }