chiark / gitweb /
resolve: add more record types and convert to gperf table
[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 uint16_t 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         r = sd_bus_message_append(req, "sqq", name, arg_class, arg_type);
320         if (r < 0)
321                 return bus_log_create_error(r);
322
323         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
324         if (r < 0) {
325                 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
326                 return r;
327         }
328
329         r = sd_bus_message_enter_container(reply, 'a', "(qqay)");
330         if (r < 0)
331                 return bus_log_parse_error(r);
332
333         while ((r = sd_bus_message_enter_container(reply, 'r', "qqay")) > 0) {
334                 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
335                 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
336                 _cleanup_free_ char *s = NULL;
337                 uint16_t c, t;
338                 const void *d;
339                 size_t l;
340
341                 r = sd_bus_message_read(reply, "qq", &c, &t);
342                 if (r < 0)
343                         return bus_log_parse_error(r);
344
345                 r = sd_bus_message_read_array(reply, 'y', &d, &l);
346                 if (r < 0)
347                         return bus_log_parse_error(r);
348
349                 r = sd_bus_message_exit_container(reply);
350                 if (r < 0)
351                         return bus_log_parse_error(r);
352
353                 r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
354                 if (r < 0)
355                         return log_oom();
356
357                 r = dns_packet_append_blob(p, d, l, NULL);
358                 if (r < 0)
359                         return log_oom();
360
361                 r = dns_packet_read_rr(p, &rr, NULL);
362                 if (r < 0) {
363                         log_error("Failed to parse RR.");
364                         return r;
365                 }
366
367                 r = dns_resource_record_to_string(rr, &s);
368                 if (r < 0) {
369                         log_error("Failed to format RR.");
370                         return r;
371                 }
372
373                 printf("%s\n", s);
374                 n++;
375         }
376         if (r < 0)
377                 return bus_log_parse_error(r);
378
379         r = sd_bus_message_exit_container(reply);
380         if (r < 0)
381                 return bus_log_parse_error(r);
382
383         if (n == 0) {
384                 log_error("%s: no records found", name);
385                 return -ESRCH;
386         }
387
388         return 0;
389 }
390
391 static void help_dns_types(void) {
392         int i;
393         const char *t;
394
395         if (arg_legend)
396                 puts("Known dns types:");
397         for (i = 0; i < _DNS_TYPE_MAX; i++) {
398                 t = dns_type_to_string(i);
399                 if (t)
400                         puts(t);
401         }
402 }
403
404 static void help_dns_classes(void) {
405         int i;
406         const char *t;
407
408         if (arg_legend)
409                 puts("Known dns classes:");
410         for (i = 0; i < _DNS_CLASS_MAX; i++) {
411                 t = dns_class_to_string(i);
412                 if (t)
413                         puts(t);
414         }
415 }
416
417 static void help(void) {
418         printf("%s [OPTIONS...]\n\n"
419                "Resolve IPv4 or IPv6 addresses.\n\n"
420                "  -h --help             Show this help\n"
421                "     --version          Show package version\n"
422                "  -4                    Resolve IPv4 addresses\n"
423                "  -6                    Resolve IPv6 addresses\n"
424                "  -i INTERFACE          Filter by interface\n"
425                "  -t --type=TYPE        Query RR with DNS type\n"
426                "  -c --class=CLASS      Query RR with DNS class\n"
427                "  --no-legend           Do not print column headers\n"
428                , program_invocation_short_name);
429 }
430
431 static int parse_argv(int argc, char *argv[]) {
432         enum {
433                 ARG_VERSION = 0x100,
434                 ARG_NO_LEGEND,
435         };
436
437         static const struct option options[] = {
438                 { "help",        no_argument,       NULL, 'h'           },
439                 { "version",     no_argument,       NULL, ARG_VERSION   },
440                 { "type",        no_argument,       NULL, 't'           },
441                 { "class",       no_argument,       NULL, 'c'           },
442                 { "no-legend",   no_argument,       NULL, ARG_NO_LEGEND },
443                 {}
444         };
445
446         int c, r;
447
448         assert(argc >= 0);
449         assert(argv);
450
451         while ((c = getopt_long(argc, argv, "h46i:t:c:", options, NULL)) >= 0)
452                 switch(c) {
453
454                 case 'h':
455                         help();
456                         return 0; /* done */;
457
458                 case ARG_VERSION:
459                         puts(PACKAGE_STRING);
460                         puts(SYSTEMD_FEATURES);
461                         return 0 /* done */;
462
463                 case '4':
464                         arg_family = AF_INET;
465                         break;
466
467                 case '6':
468                         arg_family = AF_INET6;
469                         break;
470
471                 case 'i':
472                         arg_ifindex = if_nametoindex(optarg);
473                         if (arg_ifindex <= 0) {
474                                 log_error("Unknown interfaces %s: %m", optarg);
475                                 return -errno;
476                         }
477                         break;
478
479                 case 't':
480                         if (streq(optarg, "help")) {
481                                 help_dns_types();
482                                 return 0;
483                         }
484
485                         r = dns_type_from_string(optarg, &arg_type);
486                         if (r < 0) {
487                                 log_error("Failed to parse RR record type %s", optarg);
488                                 return r;
489                         }
490
491                         break;
492
493                 case 'c':
494                         if (streq(optarg, "help")) {
495                                 help_dns_classes();
496                                 return 0;
497                         }
498
499                         r = dns_class_from_string(optarg, &arg_class);
500                         if (r < 0) {
501                                 log_error("Failed to parse RR record class %s", optarg);
502                                 return r;
503                         }
504
505                         break;
506
507                 case ARG_NO_LEGEND:
508                         arg_legend = false;
509                         break;
510
511                 case '?':
512                         return -EINVAL;
513
514                 default:
515                         assert_not_reached("Unhandled option");
516                 }
517
518         if (arg_type == 0 && arg_class != 0) {
519                 log_error("--class= may only be used in conjunction with --type=");
520                 return -EINVAL;
521         }
522
523         if (arg_type != 0 && arg_class == 0)
524                 arg_class = DNS_CLASS_IN;
525
526         return 1 /* work to do */;
527 }
528
529 int main(int argc, char **argv) {
530         _cleanup_bus_unref_ sd_bus *bus = NULL;
531         int r;
532
533         log_parse_environment();
534         log_open();
535
536         r = parse_argv(argc, argv);
537         if (r <= 0)
538                 goto finish;
539
540         if (optind >= argc) {
541                 log_error("No arguments passed");
542                 r = -EINVAL;
543                 goto finish;
544         }
545
546         r = sd_bus_open_system(&bus);
547         if (r < 0) {
548                 log_error("sd_bus_open_system: %s", strerror(-r));
549                 goto finish;
550         }
551
552         while (argv[optind]) {
553                 int family, ifindex, k;
554                 union in_addr_union a;
555
556                 if (arg_type != 0)
557                         k = resolve_record(bus, argv[optind]);
558                 else {
559                         k = parse_address(argv[optind], &family, &a, &ifindex);
560                         if (k >= 0)
561                                 k = resolve_address(bus, family, &a, ifindex);
562                         else
563                                 k = resolve_host(bus, argv[optind]);
564                 }
565
566                 if (r == 0)
567                         r = k;
568
569                 optind++;
570         }
571
572 finish:
573         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
574 }