]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nss-resolve/nss-resolve.c
nss-resolve: resue a jump target
[thirdparty/systemd.git] / src / nss-resolve / nss-resolve.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <errno.h>
4 #include <netdb.h>
5 #include <nss.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10
11 #include "sd-bus.h"
12
13 #include "bus-common-errors.h"
14 #include "errno-util.h"
15 #include "in-addr-util.h"
16 #include "macro.h"
17 #include "nss-util.h"
18 #include "resolved-def.h"
19 #include "signal-util.h"
20 #include "string-util.h"
21
22 NSS_GETHOSTBYNAME_PROTOTYPES(resolve);
23 NSS_GETHOSTBYADDR_PROTOTYPES(resolve);
24
25 static bool bus_error_shall_fallback(sd_bus_error *e) {
26 return sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
27 sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER) ||
28 sd_bus_error_has_name(e, SD_BUS_ERROR_NO_REPLY) ||
29 sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED);
30 }
31
32 static int count_addresses(sd_bus_message *m, int af, const char **canonical) {
33 int c = 0, r;
34
35 assert(m);
36 assert(canonical);
37
38 r = sd_bus_message_enter_container(m, 'a', "(iiay)");
39 if (r < 0)
40 return r;
41
42 while ((r = sd_bus_message_enter_container(m, 'r', "iiay")) > 0) {
43 int family, ifindex;
44
45 assert_cc(sizeof(int32_t) == sizeof(int));
46
47 r = sd_bus_message_read(m, "ii", &ifindex, &family);
48 if (r < 0)
49 return r;
50
51 r = sd_bus_message_skip(m, "ay");
52 if (r < 0)
53 return r;
54
55 r = sd_bus_message_exit_container(m);
56 if (r < 0)
57 return r;
58
59 if (af != AF_UNSPEC && family != af)
60 continue;
61
62 c++;
63 }
64 if (r < 0)
65 return r;
66
67 r = sd_bus_message_exit_container(m);
68 if (r < 0)
69 return r;
70
71 r = sd_bus_message_read(m, "s", canonical);
72 if (r < 0)
73 return r;
74
75 r = sd_bus_message_rewind(m, true);
76 if (r < 0)
77 return r;
78
79 return c;
80 }
81
82 static uint32_t ifindex_to_scopeid(int family, const void *a, int ifindex) {
83 struct in6_addr in6;
84
85 if (family != AF_INET6)
86 return 0;
87
88 /* Some apps can't deal with the scope ID attached to non-link-local addresses. Hence, let's suppress that. */
89
90 assert(sizeof(in6) == FAMILY_ADDRESS_SIZE(AF_INET6));
91 memcpy(&in6, a, sizeof(struct in6_addr));
92
93 return IN6_IS_ADDR_LINKLOCAL(&in6) ? ifindex : 0;
94 }
95
96 static bool avoid_deadlock(void) {
97
98 /* Check whether this lookup might have a chance of deadlocking because we are called from the service manager
99 * code activating systemd-resolved.service. After all, we shouldn't synchronously do lookups to
100 * systemd-resolved if we are required to finish before it can be started. This of course won't detect all
101 * possible dead locks of this kind, but it should work for the most obvious cases. */
102
103 if (geteuid() != 0) /* Ignore the env vars unless we are privileged. */
104 return false;
105
106 return streq_ptr(getenv("SYSTEMD_ACTIVATION_UNIT"), "systemd-resolved.service") &&
107 streq_ptr(getenv("SYSTEMD_ACTIVATION_SCOPE"), "system");
108 }
109
110 enum nss_status _nss_resolve_gethostbyname4_r(
111 const char *name,
112 struct gaih_addrtuple **pat,
113 char *buffer, size_t buflen,
114 int *errnop, int *h_errnop,
115 int32_t *ttlp) {
116
117 _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL;
118 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
119 struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL;
120 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
121 const char *canonical = NULL;
122 size_t l, ms, idx;
123 char *r_name;
124 int c, r, i = 0;
125
126 PROTECT_ERRNO;
127 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
128
129 assert(name);
130 assert(pat);
131 assert(buffer);
132 assert(errnop);
133 assert(h_errnop);
134
135 if (avoid_deadlock()) {
136 r = -EDEADLK;
137 goto fail;
138 }
139
140 r = sd_bus_open_system(&bus);
141 if (r < 0)
142 goto fail;
143
144 r = sd_bus_message_new_method_call(
145 bus,
146 &req,
147 "org.freedesktop.resolve1",
148 "/org/freedesktop/resolve1",
149 "org.freedesktop.resolve1.Manager",
150 "ResolveHostname");
151 if (r < 0)
152 goto fail;
153
154 r = sd_bus_message_set_auto_start(req, false);
155 if (r < 0)
156 goto fail;
157
158 r = sd_bus_message_append(req, "isit", 0, name, AF_UNSPEC, (uint64_t) 0);
159 if (r < 0)
160 goto fail;
161
162 r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply);
163 if (r < 0) {
164 if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN") ||
165 !bus_error_shall_fallback(&error))
166 goto not_found;
167
168 /* Return NSS_STATUS_UNAVAIL when communication with systemd-resolved fails,
169 allowing falling back to other nss modules. Treat all other error conditions as
170 NOTFOUND. This includes DNSSEC errors and suchlike. (We don't use UNAVAIL in this
171 case so that the nsswitch.conf configuration can distuingish such executed but
172 negative replies from complete failure to talk to resolved). */
173 goto fail;
174 }
175
176 c = count_addresses(reply, AF_UNSPEC, &canonical);
177 if (c < 0) {
178 r = c;
179 goto fail;
180 }
181 if (c == 0)
182 goto not_found;
183
184 if (isempty(canonical))
185 canonical = name;
186
187 l = strlen(canonical);
188 ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c;
189 if (buflen < ms) {
190 UNPROTECT_ERRNO;
191 *errnop = ERANGE;
192 *h_errnop = NETDB_INTERNAL;
193 return NSS_STATUS_TRYAGAIN;
194 }
195
196 /* First, append name */
197 r_name = buffer;
198 memcpy(r_name, canonical, l+1);
199 idx = ALIGN(l+1);
200
201 /* Second, append addresses */
202 r_tuple_first = (struct gaih_addrtuple*) (buffer + idx);
203
204 r = sd_bus_message_enter_container(reply, 'a', "(iiay)");
205 if (r < 0)
206 goto fail;
207
208 while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) {
209 int family, ifindex;
210 const void *a;
211 size_t sz;
212
213 assert_cc(sizeof(int32_t) == sizeof(int));
214
215 r = sd_bus_message_read(reply, "ii", &ifindex, &family);
216 if (r < 0)
217 goto fail;
218
219 if (ifindex < 0) {
220 r = -EINVAL;
221 goto fail;
222 }
223
224 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
225 if (r < 0)
226 goto fail;
227
228 r = sd_bus_message_exit_container(reply);
229 if (r < 0)
230 goto fail;
231
232 if (!IN_SET(family, AF_INET, AF_INET6))
233 continue;
234
235 if (sz != FAMILY_ADDRESS_SIZE(family)) {
236 r = -EINVAL;
237 goto fail;
238 }
239
240 r_tuple = (struct gaih_addrtuple*) (buffer + idx);
241 r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple)));
242 r_tuple->name = r_name;
243 r_tuple->family = family;
244 r_tuple->scopeid = ifindex_to_scopeid(family, a, ifindex);
245 memcpy(r_tuple->addr, a, sz);
246
247 idx += ALIGN(sizeof(struct gaih_addrtuple));
248 i++;
249 }
250 if (r < 0)
251 goto fail;
252
253 assert(i == c);
254 assert(idx == ms);
255
256 if (*pat)
257 **pat = *r_tuple_first;
258 else
259 *pat = r_tuple_first;
260
261 if (ttlp)
262 *ttlp = 0;
263
264 /* Explicitly reset both *h_errnop and h_errno to work around
265 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
266 *h_errnop = NETDB_SUCCESS;
267 h_errno = 0;
268
269 return NSS_STATUS_SUCCESS;
270
271 fail:
272 UNPROTECT_ERRNO;
273 *errnop = -r;
274 *h_errnop = NO_RECOVERY;
275 return NSS_STATUS_UNAVAIL;
276
277 not_found:
278 *h_errnop = HOST_NOT_FOUND;
279 return NSS_STATUS_NOTFOUND;
280 }
281
282 enum nss_status _nss_resolve_gethostbyname3_r(
283 const char *name,
284 int af,
285 struct hostent *result,
286 char *buffer, size_t buflen,
287 int *errnop, int *h_errnop,
288 int32_t *ttlp,
289 char **canonp) {
290
291 _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL;
292 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
293 char *r_name, *r_aliases, *r_addr, *r_addr_list;
294 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
295 size_t l, idx, ms, alen;
296 const char *canonical;
297 int c, r, i = 0;
298
299 PROTECT_ERRNO;
300 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
301
302 assert(name);
303 assert(result);
304 assert(buffer);
305 assert(errnop);
306 assert(h_errnop);
307
308 if (af == AF_UNSPEC)
309 af = AF_INET;
310
311 if (!IN_SET(af, AF_INET, AF_INET6)) {
312 r = -EAFNOSUPPORT;
313 goto fail;
314 }
315
316 if (avoid_deadlock()) {
317 r = -EDEADLK;
318 goto fail;
319 }
320
321 r = sd_bus_open_system(&bus);
322 if (r < 0)
323 goto fail;
324
325 r = sd_bus_message_new_method_call(
326 bus,
327 &req,
328 "org.freedesktop.resolve1",
329 "/org/freedesktop/resolve1",
330 "org.freedesktop.resolve1.Manager",
331 "ResolveHostname");
332 if (r < 0)
333 goto fail;
334
335 r = sd_bus_message_set_auto_start(req, false);
336 if (r < 0)
337 goto fail;
338
339 r = sd_bus_message_append(req, "isit", 0, name, af, (uint64_t) 0);
340 if (r < 0)
341 goto fail;
342
343 r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply);
344 if (r < 0) {
345 if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN") ||
346 !bus_error_shall_fallback(&error))
347 goto not_found;
348
349 goto fail;
350 }
351
352 c = count_addresses(reply, af, &canonical);
353 if (c < 0) {
354 r = c;
355 goto fail;
356 }
357 if (c == 0)
358 goto not_found;
359
360 if (isempty(canonical))
361 canonical = name;
362
363 alen = FAMILY_ADDRESS_SIZE(af);
364 l = strlen(canonical);
365
366 ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*);
367
368 if (buflen < ms) {
369 UNPROTECT_ERRNO;
370 *errnop = ERANGE;
371 *h_errnop = NETDB_INTERNAL;
372 return NSS_STATUS_TRYAGAIN;
373 }
374
375 /* First, append name */
376 r_name = buffer;
377 memcpy(r_name, canonical, l+1);
378 idx = ALIGN(l+1);
379
380 /* Second, create empty aliases array */
381 r_aliases = buffer + idx;
382 ((char**) r_aliases)[0] = NULL;
383 idx += sizeof(char*);
384
385 /* Third, append addresses */
386 r_addr = buffer + idx;
387
388 r = sd_bus_message_enter_container(reply, 'a', "(iiay)");
389 if (r < 0)
390 goto fail;
391
392 while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) {
393 int ifindex, family;
394 const void *a;
395 size_t sz;
396
397 r = sd_bus_message_read(reply, "ii", &ifindex, &family);
398 if (r < 0)
399 goto fail;
400
401 if (ifindex < 0) {
402 r = -EINVAL;
403 goto fail;
404 }
405
406 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
407 if (r < 0)
408 goto fail;
409
410 r = sd_bus_message_exit_container(reply);
411 if (r < 0)
412 goto fail;
413
414 if (family != af)
415 continue;
416
417 if (sz != alen) {
418 r = -EINVAL;
419 goto fail;
420 }
421
422 memcpy(r_addr + i*ALIGN(alen), a, alen);
423 i++;
424 }
425 if (r < 0)
426 goto fail;
427
428 assert(i == c);
429 idx += c * ALIGN(alen);
430
431 /* Fourth, append address pointer array */
432 r_addr_list = buffer + idx;
433 for (i = 0; i < c; i++)
434 ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen);
435
436 ((char**) r_addr_list)[i] = NULL;
437 idx += (c+1) * sizeof(char*);
438
439 assert(idx == ms);
440
441 result->h_name = r_name;
442 result->h_aliases = (char**) r_aliases;
443 result->h_addrtype = af;
444 result->h_length = alen;
445 result->h_addr_list = (char**) r_addr_list;
446
447 if (ttlp)
448 *ttlp = 0;
449
450 if (canonp)
451 *canonp = r_name;
452
453 /* Explicitly reset both *h_errnop and h_errno to work around
454 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
455 *h_errnop = NETDB_SUCCESS;
456 h_errno = 0;
457
458 return NSS_STATUS_SUCCESS;
459
460 fail:
461 UNPROTECT_ERRNO;
462 *errnop = -r;
463 *h_errnop = NO_RECOVERY;
464 return NSS_STATUS_UNAVAIL;
465
466 not_found:
467 *h_errnop = HOST_NOT_FOUND;
468 return NSS_STATUS_NOTFOUND;
469 }
470
471 enum nss_status _nss_resolve_gethostbyaddr2_r(
472 const void* addr, socklen_t len,
473 int af,
474 struct hostent *result,
475 char *buffer, size_t buflen,
476 int *errnop, int *h_errnop,
477 int32_t *ttlp) {
478
479 _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL;
480 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
481 char *r_name, *r_aliases, *r_addr, *r_addr_list;
482 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
483 unsigned c = 0, i = 0;
484 size_t ms = 0, idx;
485 const char *n;
486 int r, ifindex;
487
488 PROTECT_ERRNO;
489 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
490
491 assert(addr);
492 assert(result);
493 assert(buffer);
494 assert(errnop);
495 assert(h_errnop);
496
497 if (!IN_SET(af, AF_INET, AF_INET6)) {
498 UNPROTECT_ERRNO;
499 *errnop = EAFNOSUPPORT;
500 *h_errnop = NO_DATA;
501 return NSS_STATUS_UNAVAIL;
502 }
503
504 if (len != FAMILY_ADDRESS_SIZE(af)) {
505 r = -EINVAL;
506 goto fail;
507 }
508
509 if (avoid_deadlock()) {
510 r = -EDEADLK;
511 goto fail;
512 }
513
514 r = sd_bus_open_system(&bus);
515 if (r < 0)
516 goto fail;
517
518 r = sd_bus_message_new_method_call(
519 bus,
520 &req,
521 "org.freedesktop.resolve1",
522 "/org/freedesktop/resolve1",
523 "org.freedesktop.resolve1.Manager",
524 "ResolveAddress");
525 if (r < 0)
526 goto fail;
527
528 r = sd_bus_message_set_auto_start(req, false);
529 if (r < 0)
530 goto fail;
531
532 r = sd_bus_message_append(req, "ii", 0, af);
533 if (r < 0)
534 goto fail;
535
536 r = sd_bus_message_append_array(req, 'y', addr, len);
537 if (r < 0)
538 goto fail;
539
540 r = sd_bus_message_append(req, "t", (uint64_t) 0);
541 if (r < 0)
542 goto fail;
543
544 r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply);
545 if (r < 0) {
546 if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN") ||
547 !bus_error_shall_fallback(&error))
548 goto not_found;
549
550 goto fail;
551 }
552
553 r = sd_bus_message_enter_container(reply, 'a', "(is)");
554 if (r < 0)
555 goto fail;
556
557 while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) {
558
559 if (ifindex < 0) {
560 r = -EINVAL;
561 goto fail;
562 }
563
564 c++;
565 ms += ALIGN(strlen(n) + 1);
566 }
567 if (r < 0)
568 goto fail;
569
570 r = sd_bus_message_rewind(reply, false);
571 if (r < 0)
572 goto fail;
573
574 if (c <= 0)
575 goto not_found;
576
577 ms += ALIGN(len) + /* the address */
578 2 * sizeof(char*) + /* pointers to the address, plus trailing NULL */
579 c * sizeof(char*); /* pointers to aliases, plus trailing NULL */
580
581 if (buflen < ms) {
582 UNPROTECT_ERRNO;
583 *errnop = ERANGE;
584 *h_errnop = NETDB_INTERNAL;
585 return NSS_STATUS_TRYAGAIN;
586 }
587
588 /* First, place address */
589 r_addr = buffer;
590 memcpy(r_addr, addr, len);
591 idx = ALIGN(len);
592
593 /* Second, place address list */
594 r_addr_list = buffer + idx;
595 ((char**) r_addr_list)[0] = r_addr;
596 ((char**) r_addr_list)[1] = NULL;
597 idx += sizeof(char*) * 2;
598
599 /* Third, reserve space for the aliases array */
600 r_aliases = buffer + idx;
601 idx += sizeof(char*) * c;
602
603 /* Fourth, place aliases */
604 i = 0;
605 r_name = buffer + idx;
606 while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) {
607 char *p;
608 size_t l;
609
610 l = strlen(n);
611 p = buffer + idx;
612 memcpy(p, n, l+1);
613
614 if (i > 0)
615 ((char**) r_aliases)[i-1] = p;
616 i++;
617
618 idx += ALIGN(l+1);
619 }
620 if (r < 0)
621 goto fail;
622
623 ((char**) r_aliases)[c-1] = NULL;
624 assert(idx == ms);
625
626 result->h_name = r_name;
627 result->h_aliases = (char**) r_aliases;
628 result->h_addrtype = af;
629 result->h_length = len;
630 result->h_addr_list = (char**) r_addr_list;
631
632 if (ttlp)
633 *ttlp = 0;
634
635 /* Explicitly reset both *h_errnop and h_errno to work around
636 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
637 *h_errnop = NETDB_SUCCESS;
638 h_errno = 0;
639
640 return NSS_STATUS_SUCCESS;
641
642 fail:
643 UNPROTECT_ERRNO;
644 *errnop = -r;
645 *h_errnop = NO_RECOVERY;
646 return NSS_STATUS_UNAVAIL;
647
648 not_found:
649 *h_errnop = HOST_NOT_FOUND;
650 return NSS_STATUS_NOTFOUND;
651 }
652
653 NSS_GETHOSTBYNAME_FALLBACKS(resolve);
654 NSS_GETHOSTBYADDR_FALLBACKS(resolve);