chiark / gitweb /
1b1edaf6ec73be872154382c2238f6d10d9155a1
[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
43 static int resolve_host(sd_bus *bus, const char *name) {
44
45         _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
46         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
47         const char *canonical = NULL;
48         unsigned c = 0;
49         int r;
50
51         assert(name);
52
53         log_debug("Resolving %s (family %s, ifindex %i).", name, af_to_name(arg_family) ?: "*", arg_ifindex);
54
55         r = sd_bus_message_new_method_call(
56                         bus,
57                         &req,
58                         "org.freedesktop.resolve1",
59                         "/org/freedesktop/resolve1",
60                         "org.freedesktop.resolve1.Manager",
61                         "ResolveHostname");
62         if (r < 0)
63                 return bus_log_create_error(r);
64
65         r = sd_bus_message_set_auto_start(req, false);
66         if (r < 0)
67                 return bus_log_create_error(r);
68
69         r = sd_bus_message_append(req, "si", name, arg_family);
70         if (r < 0)
71                 return bus_log_create_error(r);
72
73         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
74         if (r < 0) {
75                 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
76                 return r;
77         }
78
79         r = sd_bus_message_enter_container(reply, 'a', "(iayi)");
80         if (r < 0)
81                 return bus_log_parse_error(r);
82
83         while ((r = sd_bus_message_enter_container(reply, 'r', "iayi")) > 0) {
84                 const void *a;
85                 int family, ifindex;
86                 size_t sz;
87                 _cleanup_free_ char *pretty = NULL;
88                 char ifname[IF_NAMESIZE] = "";
89
90                 r = sd_bus_message_read(reply, "i", &family);
91                 if (r < 0)
92                         return bus_log_parse_error(r);
93
94                 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
95                 if (r < 0)
96                         return bus_log_parse_error(r);
97
98                 r = sd_bus_message_read(reply, "i", &ifindex);
99                 if (r < 0)
100                         return bus_log_parse_error(r);
101
102                 r = sd_bus_message_exit_container(reply);
103                 if (r < 0)
104                         return bus_log_parse_error(r);
105
106                 if (!IN_SET(family, AF_INET, AF_INET6)) {
107                         log_debug("%s: skipping entry with family %hu (%s)", name, family, af_to_name(family) ?: "unknown");
108                         continue;
109                 }
110
111                 if (sz != FAMILY_ADDRESS_SIZE(family)) {
112                         log_error("%s: systemd-resolved returned address of invalid size %zu for family %s",
113                                   name, sz, af_to_name(family) ?: "unknown");
114                         continue;
115                 }
116
117                 if (ifindex < 0) {
118                         log_error("%s: systemd-resolved returned invalid interface index %i",
119                                   name, ifindex);
120                         continue;
121                 }
122
123                 if (ifindex > 0) {
124                         char *t;
125
126                         t = if_indextoname(ifindex, ifname);
127                         if (!t) {
128                                 log_error("Failed to resolve interface name for index %i", ifindex);
129                                 continue;
130                         }
131                 }
132
133                 if (arg_ifindex > 0 && ifindex > 0 && ifindex != arg_ifindex) {
134                         log_debug("%s: skipping entry with ifindex %i (%s)",
135                                   name, ifindex, ifname);
136                         continue;
137                 }
138
139                 r = in_addr_to_string(family, a, &pretty);
140                 if (r < 0) {
141                         log_error("%s: failed to print address: %s", name, strerror(-r));
142                         continue;
143                 }
144
145                 printf("%*s%s %s%s%s\n",
146                        (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
147                        pretty,
148                        isempty(ifname) ? "" : "%", ifname);
149
150                 c++;
151         }
152         if (r < 0)
153                 return bus_log_parse_error(r);
154
155         r = sd_bus_message_exit_container(reply);
156         if (r < 0)
157                 return bus_log_parse_error(r);
158
159         r = sd_bus_message_read(reply, "s", &canonical);
160         if (r < 0)
161                 return bus_log_parse_error(r);
162
163         if (!streq(name, canonical)) {
164                 printf("%*s%s (%s)\n",
165                        (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
166                        canonical);
167         }
168
169         if (c == 0) {
170                 log_error("%s: no addresses found", name);
171                 return -ESRCH;
172         }
173
174         return 0;
175 }
176
177 static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) {
178         _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
179         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
180         _cleanup_free_ char *pretty = NULL;
181         char ifname[IF_NAMESIZE] = "";
182         unsigned c = 0;
183         const char *n;
184         int r;
185
186         assert(bus);
187         assert(IN_SET(family, AF_INET, AF_INET6));
188         assert(address);
189
190         r = in_addr_to_string(family, address, &pretty);
191         if (r < 0)
192                 return log_oom();
193
194         if (ifindex > 0) {
195                 char *t;
196
197                 t = if_indextoname(ifindex, ifname);
198                 if (!t) {
199                         log_error("Failed to resolve interface name for index %i", ifindex);
200                         return -errno;
201                 }
202         }
203
204         log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname);
205
206         r = sd_bus_message_new_method_call(
207                         bus,
208                         &req,
209                         "org.freedesktop.resolve1",
210                         "/org/freedesktop/resolve1",
211                         "org.freedesktop.resolve1.Manager",
212                         "ResolveAddress");
213         if (r < 0)
214                 return bus_log_create_error(r);
215
216         r = sd_bus_message_set_auto_start(req, false);
217         if (r < 0)
218                 return bus_log_create_error(r);
219
220         r = sd_bus_message_append(req, "i", family);
221         if (r < 0)
222                 return bus_log_create_error(r);
223
224         r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family));
225         if (r < 0)
226                 return bus_log_create_error(r);
227
228         r = sd_bus_message_append(req, "i", ifindex);
229         if (r < 0)
230                 return bus_log_create_error(r);
231
232         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
233         if (r < 0) {
234                 log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r));
235                 return r;
236         }
237
238         r = sd_bus_message_enter_container(reply, 'a', "s");
239         if (r < 0)
240                 return bus_log_create_error(r);
241
242         while ((r = sd_bus_message_read(reply, "s", &n)) > 0) {
243
244                 printf("%*s%s%s%s %s\n",
245                        (int) strlen(pretty), c == 0 ? pretty : "",
246                        isempty(ifname) ? "" : "%", ifname,
247                        c == 0 ? ":" : " ",
248                        n);
249
250                 c++;
251         }
252         if (r < 0)
253                 return bus_log_parse_error(r);
254
255         r = sd_bus_message_exit_container(reply);
256         if (r < 0)
257                 return bus_log_parse_error(r);
258
259         if (c == 0) {
260                 log_error("%s: no names found", pretty);
261                 return -ESRCH;
262         }
263
264         return 0;
265 }
266
267 static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) {
268         const char *percent, *a;
269         int ifi = 0;
270         int r;
271
272         percent = strchr(s, '%');
273         if (percent) {
274                 r = safe_atoi(percent+1, &ifi);
275                 if (r < 0 || ifi <= 0) {
276                         ifi = if_nametoindex(percent+1);
277                         if (ifi <= 0)
278                                 return -EINVAL;
279                 }
280
281                 a = strndupa(s, percent - s);
282         } else
283                 a = s;
284
285         r = in_addr_from_string_auto(a, family, address);
286         if (r < 0)
287                 return r;
288
289         *ifindex = ifi;
290         return 0;
291 }
292
293 static int resolve_record(sd_bus *bus, const char *name) {
294
295         _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
296         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
297         unsigned n = 0;
298         int r;
299
300         assert(name);
301
302         log_debug("Resolving %s %s %s.", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type));
303
304         r = sd_bus_message_new_method_call(
305                         bus,
306                         &req,
307                         "org.freedesktop.resolve1",
308                         "/org/freedesktop/resolve1",
309                         "org.freedesktop.resolve1.Manager",
310                         "ResolveRecord");
311         if (r < 0)
312                 return bus_log_create_error(r);
313
314         r = sd_bus_message_set_auto_start(req, false);
315         if (r < 0)
316                 return bus_log_create_error(r);
317
318         r = sd_bus_message_append(req, "sqq", name, arg_class, arg_type);
319         if (r < 0)
320                 return bus_log_create_error(r);
321
322         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
323         if (r < 0) {
324                 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
325                 return r;
326         }
327
328         r = sd_bus_message_enter_container(reply, 'a', "(qqay)");
329         if (r < 0)
330                 return bus_log_parse_error(r);
331
332         while ((r = sd_bus_message_enter_container(reply, 'r', "qqay")) > 0) {
333                 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
334                 _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
335                 _cleanup_free_ char *s = NULL;
336                 uint16_t c, t;
337                 const void *d;
338                 size_t l;
339
340                 r = sd_bus_message_read(reply, "qq", &c, &t);
341                 if (r < 0)
342                         return bus_log_parse_error(r);
343
344                 r = sd_bus_message_read_array(reply, 'y', &d, &l);
345                 if (r < 0)
346                         return bus_log_parse_error(r);
347
348                 r = sd_bus_message_exit_container(reply);
349                 if (r < 0)
350                         return bus_log_parse_error(r);
351
352                 r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
353                 if (r < 0)
354                         return log_oom();
355
356                 r = dns_packet_append_blob(p, d, l, NULL);
357                 if (r < 0)
358                         return log_oom();
359
360                 r = dns_packet_read_rr(p, &rr, NULL);
361                 if (r < 0) {
362                         log_error("Failed to parse RR.");
363                         return r;
364                 }
365
366                 r = dns_resource_record_to_string(rr, &s);
367                 if (r < 0) {
368                         log_error("Failed to format RR.");
369                         return r;
370                 }
371
372                 printf("%s\n", s);
373                 n++;
374         }
375         if (r < 0)
376                 return bus_log_parse_error(r);
377
378         r = sd_bus_message_exit_container(reply);
379         if (r < 0)
380                 return bus_log_parse_error(r);
381
382         if (n == 0) {
383                 log_error("%s: no records found", name);
384                 return -ESRCH;
385         }
386
387         return 0;
388 }
389
390 static void help(void) {
391         printf("%s [OPTIONS...]\n\n"
392                "Resolve IPv4 or IPv6 addresses.\n\n"
393                "  -h --help             Show this help\n"
394                "     --version          Show package version\n"
395                "  -4                    Resolve IPv4 addresses\n"
396                "  -6                    Resolve IPv6 addresses\n"
397                "  -i INTERFACE          Filter by interface\n"
398                "  -t --type=TYPE        Query RR with DNS type\n"
399                "  -c --class=CLASS      Query RR with DNS class\n"
400                , program_invocation_short_name);
401 }
402
403 static int parse_argv(int argc, char *argv[]) {
404         enum {
405                 ARG_VERSION = 0x100,
406         };
407
408         static const struct option options[] = {
409                 { "help",        no_argument,       NULL, 'h'           },
410                 { "version",     no_argument,       NULL, ARG_VERSION   },
411                 { "type",        no_argument,       NULL, 't'           },
412                 { "class",       no_argument,       NULL, 'c'           },
413                 {}
414         };
415
416         int c, r;
417
418         assert(argc >= 0);
419         assert(argv);
420
421         while ((c = getopt_long(argc, argv, "h46i:t:c:", options, NULL)) >= 0) {
422                 switch(c) {
423
424                 case 'h':
425                         help();
426                         return 0; /* done */;
427
428                 case ARG_VERSION:
429                         puts(PACKAGE_STRING);
430                         puts(SYSTEMD_FEATURES);
431                         return 0 /* done */;
432
433                 case '4':
434                         arg_family = AF_INET;
435                         break;
436
437                 case '6':
438                         arg_family = AF_INET6;
439                         break;
440
441                 case 'i':
442                         arg_ifindex = if_nametoindex(optarg);
443                         if (arg_ifindex <= 0) {
444                                 log_error("Unknown interfaces %s: %m", optarg);
445                                 return -errno;
446                         }
447                         break;
448
449                 case 't':
450                         r = dns_type_from_string(optarg, &arg_type);
451                         if (r < 0) {
452                                 log_error("Failed to parse RR record type %s", optarg);
453                                 return r;
454                         }
455                         break;
456
457                 case 'c':
458                         r = dns_class_from_string(optarg, &arg_class);
459                         if (r < 0) {
460                                 log_error("Failed to parse RR record class %s", optarg);
461                                 return r;
462                         }
463                         break;
464
465                 case '?':
466                         return -EINVAL;
467
468                 default:
469                         assert_not_reached("Unhandled option");
470                 }
471         }
472
473         if (arg_type == 0 && arg_class != 0) {
474                 log_error("--class= may only be used in conjunction with --type=");
475                 return -EINVAL;
476         }
477
478         if (arg_type != 0 && arg_class == 0)
479                 arg_class = DNS_CLASS_IN;
480
481         return 1 /* work to do */;
482 }
483
484 int main(int argc, char **argv) {
485         _cleanup_bus_unref_ sd_bus *bus = NULL;
486         int r;
487
488         log_parse_environment();
489         log_open();
490
491         r = parse_argv(argc, argv);
492         if (r <= 0)
493                 goto finish;
494
495         if (optind >= argc) {
496                 log_error("No arguments passed");
497                 r = -EINVAL;
498                 goto finish;
499         }
500
501         r = sd_bus_open_system(&bus);
502         if (r < 0) {
503                 log_error("sd_bus_open_system: %s", strerror(-r));
504                 goto finish;
505         }
506
507         while (argv[optind]) {
508                 int family, ifindex, k;
509                 union in_addr_union a;
510
511                 if (arg_type != 0)
512                         k = resolve_record(bus, argv[optind]);
513                 else {
514                         k = parse_address(argv[optind], &family, &a, &ifindex);
515                         if (k >= 0)
516                                 k = resolve_address(bus, family, &a, ifindex);
517                         else
518                                 k = resolve_host(bus, argv[optind]);
519                 }
520
521                 if (r == 0)
522                         r = k;
523
524                 optind++;
525         }
526
527 finish:
528         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
529 }