chiark / gitweb /
resolve-host: add reverse lookup support
[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 #define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC)
35
36 static int arg_family = AF_UNSPEC;
37 static int arg_ifindex = 0;
38
39 static int resolve_host(sd_bus *bus, const char *name) {
40
41         _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
42         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
43         const char *canonical = NULL;
44         unsigned c = 0;
45         int r;
46
47         assert(name);
48
49         log_debug("Resolving %s (family %s, ifindex %i).", name, af_to_name(arg_family) ?: "*", arg_ifindex);
50
51         r = sd_bus_message_new_method_call(
52                         bus,
53                         &req,
54                         "org.freedesktop.resolve1",
55                         "/org/freedesktop/resolve1",
56                         "org.freedesktop.resolve1.Manager",
57                         "ResolveHostname");
58         if (r < 0)
59                 return bus_log_create_error(r);
60
61         r = sd_bus_message_set_auto_start(req, false);
62         if (r < 0)
63                 return bus_log_create_error(r);
64
65         r = sd_bus_message_append(req, "si", name, arg_family);
66         if (r < 0)
67                 return bus_log_create_error(r);
68
69         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
70         if (r < 0) {
71                 log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r));
72                 return r;
73         }
74
75         r = sd_bus_message_enter_container(reply, 'a', "(iayi)");
76         if (r < 0)
77                 return bus_log_parse_error(r);
78
79         while ((r = sd_bus_message_enter_container(reply, 'r', "iayi")) > 0) {
80                 const void *a;
81                 int family, ifindex;
82                 size_t sz;
83                 _cleanup_free_ char *pretty = NULL;
84                 char ifname[IF_NAMESIZE] = "";
85
86                 r = sd_bus_message_read(reply, "i", &family);
87                 if (r < 0)
88                         return bus_log_parse_error(r);
89
90                 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
91                 if (r < 0)
92                         return bus_log_parse_error(r);
93
94                 r = sd_bus_message_read(reply, "i", &ifindex);
95                 if (r < 0)
96                         return bus_log_parse_error(r);
97
98                 r = sd_bus_message_exit_container(reply);
99                 if (r < 0)
100                         return bus_log_parse_error(r);
101
102                 if (!IN_SET(family, AF_INET, AF_INET6)) {
103                         log_debug("%s: skipping entry with family %hu (%s)", name, family, af_to_name(family) ?: "unknown");
104                         continue;
105                 }
106
107                 if (sz != FAMILY_ADDRESS_SIZE(family)) {
108                         log_error("%s: systemd-resolved returned address of invalid size %zu for family %s",
109                                   name, sz, af_to_name(family) ?: "unknown");
110                         continue;
111                 }
112
113                 if (ifindex < 0) {
114                         log_error("%s: systemd-resolved returned invalid interface index %i",
115                                   name, ifindex);
116                         continue;
117                 }
118
119                 if (ifindex > 0) {
120                         char *t;
121
122                         t = if_indextoname(ifindex, ifname);
123                         if (!t) {
124                                 log_error("Failed to resolve interface name for index %i", ifindex);
125                                 continue;
126                         }
127                 }
128
129                 if (arg_ifindex > 0 && ifindex > 0 && ifindex != arg_ifindex) {
130                         log_debug("%s: skipping entry with ifindex %i (%s)",
131                                   name, ifindex, ifname);
132                         continue;
133                 }
134
135                 r = in_addr_to_string(family, a, &pretty);
136                 if (r < 0) {
137                         log_error("%s: failed to print address: %s", name, strerror(-r));
138                         continue;
139                 }
140
141                 printf("%*s%s %s%s%s\n",
142                        (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
143                        pretty,
144                        isempty(ifname) ? "" : "%", ifname);
145
146                 c++;
147         }
148         if (r < 0)
149                 return bus_log_parse_error(r);
150
151         r = sd_bus_message_exit_container(reply);
152         if (r < 0)
153                 return bus_log_parse_error(r);
154
155         r = sd_bus_message_read(reply, "s", &canonical);
156         if (r < 0)
157                 return bus_log_parse_error(r);
158
159         if (!streq(name, canonical)) {
160                 printf("%*s%s (%s)\n",
161                        (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ",
162                        canonical);
163         }
164
165         if (c == 0) {
166                 log_error("%s: no addresses found", name);
167                 return -ESRCH;
168         }
169
170         return 0;
171 }
172
173 static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) {
174         _cleanup_bus_message_unref_ sd_bus_message *req = NULL, *reply = NULL;
175         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
176         _cleanup_free_ char *pretty = NULL;
177         char ifname[IF_NAMESIZE] = "";
178         unsigned c = 0;
179         const char *n;
180         int r;
181
182         assert(bus);
183         assert(IN_SET(family, AF_INET, AF_INET6));
184         assert(address);
185
186         r = in_addr_to_string(family, address, &pretty);
187         if (r < 0)
188                 return log_oom();
189
190         if (ifindex > 0) {
191                 char *t;
192
193                 t = if_indextoname(ifindex, ifname);
194                 if (!t) {
195                         log_error("Failed to resolve interface name for index %i", ifindex);
196                         return -errno;
197                 }
198         }
199
200         log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname);
201
202         r = sd_bus_message_new_method_call(
203                         bus,
204                         &req,
205                         "org.freedesktop.resolve1",
206                         "/org/freedesktop/resolve1",
207                         "org.freedesktop.resolve1.Manager",
208                         "ResolveAddress");
209         if (r < 0)
210                 return bus_log_create_error(r);
211
212         r = sd_bus_message_set_auto_start(req, false);
213         if (r < 0)
214                 return bus_log_create_error(r);
215
216         r = sd_bus_message_append(req, "i", family);
217         if (r < 0)
218                 return bus_log_create_error(r);
219
220         r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family));
221         if (r < 0)
222                 return bus_log_create_error(r);
223
224         r = sd_bus_message_append(req, "i", ifindex);
225         if (r < 0)
226                 return bus_log_create_error(r);
227
228         r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply);
229         if (r < 0) {
230                 log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r));
231                 return r;
232         }
233
234         r = sd_bus_message_enter_container(reply, 'a', "s");
235         if (r < 0)
236                 return bus_log_create_error(r);
237
238         while ((r = sd_bus_message_read(reply, "s", &n)) > 0) {
239
240                 printf("%*s%s%s%s %s\n",
241                        (int) strlen(pretty), c == 0 ? pretty : "",
242                        isempty(ifname) ? "" : "%", ifname,
243                        c == 0 ? ":" : " ",
244                        n);
245
246                 c++;
247         }
248         if (r < 0)
249                 return bus_log_parse_error(r);
250
251         r = sd_bus_message_exit_container(reply);
252         if (r < 0)
253                 return bus_log_parse_error(r);
254
255         if (c == 0) {
256                 log_error("%s: no names found", pretty);
257                 return -ESRCH;
258         }
259
260         return 0;
261 }
262
263 static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) {
264         const char *percent, *a;
265         int ifi = 0;
266         int r;
267
268         percent = strchr(s, '%');
269         if (percent) {
270                 r = safe_atoi(percent+1, &ifi);
271                 if (r < 0 || ifi <= 0) {
272                         ifi = if_nametoindex(percent+1);
273                         if (ifi <= 0)
274                                 return -EINVAL;
275                 }
276
277                 a = strndupa(s, percent - s);
278         } else
279                 a = s;
280
281         r = in_addr_from_string_auto(a, family, address);
282         if (r < 0)
283                 return r;
284
285         *ifindex = ifi;
286         return 0;
287 }
288
289 static void help(void) {
290         printf("%s [OPTIONS...]\n\n"
291                "Resolve IPv4 or IPv6 addresses.\n\n"
292                "  -h --help             Show this help\n"
293                "     --version          Show package version\n"
294                "  -4                    Resolve IPv4 addresses\n"
295                "  -6                    Resolve IPv6 addresses\n"
296                "  -i INTERFACE          Filter by interface\n"
297                , program_invocation_short_name);
298 }
299
300 static int parse_argv(int argc, char *argv[]) {
301         enum {
302                 ARG_VERSION = 0x100,
303         };
304
305         static const struct option options[] = {
306                 { "help",        no_argument,       NULL, 'h'           },
307                 { "version",     no_argument,       NULL, ARG_VERSION   },
308                 {}
309         };
310
311         int c;
312
313         assert(argc >= 0);
314         assert(argv);
315
316         while ((c = getopt_long(argc, argv, "h46i:", options, NULL)) >= 0) {
317                 switch(c) {
318
319                 case 'h':
320                         help();
321                         return 0; /* done */;
322
323                 case ARG_VERSION:
324                         puts(PACKAGE_STRING);
325                         puts(SYSTEMD_FEATURES);
326                         return 0 /* done */;
327
328                 case '4':
329                         arg_family = AF_INET;
330                         break;
331
332                 case '6':
333                         arg_family = AF_INET6;
334                         break;
335
336                 case 'i':
337                         arg_ifindex = if_nametoindex(optarg);
338                         if (arg_ifindex <= 0) {
339                                 log_error("Unknown interfaces %s: %m", optarg);
340                                 return -EINVAL;
341                         }
342                         break;
343
344                 case '?':
345                         return -EINVAL;
346
347                 default:
348                         assert_not_reached("Unhandled option");
349                 }
350         }
351
352         return 1 /* work to do */;
353 }
354
355 int main(int argc, char **argv) {
356         _cleanup_bus_unref_ sd_bus *bus = NULL;
357         int r;
358
359         log_parse_environment();
360         log_open();
361
362         r = parse_argv(argc, argv);
363         if (r <= 0)
364                 goto finish;
365
366         if (optind >= argc) {
367                 log_error("No arguments passed");
368                 r = -EINVAL;
369                 goto finish;
370         }
371
372         r = sd_bus_open_system(&bus);
373         if (r < 0) {
374                 log_error("sd_bus_open_system: %s", strerror(-r));
375                 goto finish;
376         }
377
378         while (argv[optind]) {
379                 int family, ifindex, k;
380                 union in_addr_union a;
381
382                 k = parse_address(argv[optind], &family, &a, &ifindex);
383                 if (k >= 0)
384                         k = resolve_address(bus, family, &a, ifindex);
385                 else
386                         k = resolve_host(bus, argv[optind]);
387
388                 if (r == 0)
389                         r = k;
390
391                 optind++;
392         }
393
394 finish:
395         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
396 }