1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
12 #include "errno-util.h"
13 #include "in-addr-util.h"
16 #include "resolved-def.h"
17 #include "signal-util.h"
18 #include "string-util.h"
22 static JsonDispatchFlags json_dispatch_flags
= 0;
24 static void setup_logging(void) {
25 log_parse_environment();
28 json_dispatch_flags
= JSON_LOG
;
31 static void setup_logging_once(void) {
32 static pthread_once_t once
= PTHREAD_ONCE_INIT
;
33 assert_se(pthread_once(&once
, setup_logging
) == 0);
36 #define NSS_ENTRYPOINT_BEGIN \
37 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); \
40 NSS_GETHOSTBYNAME_PROTOTYPES(resolve
);
41 NSS_GETHOSTBYADDR_PROTOTYPES(resolve
);
43 static bool error_shall_fallback(const char *error_id
) {
44 return STR_IN_SET(error_id
,
45 VARLINK_ERROR_DISCONNECTED
,
46 VARLINK_ERROR_TIMEOUT
,
47 VARLINK_ERROR_PROTOCOL
,
48 VARLINK_ERROR_INTERFACE_NOT_FOUND
,
49 VARLINK_ERROR_METHOD_NOT_FOUND
,
50 VARLINK_ERROR_METHOD_NOT_IMPLEMENTED
);
53 static int connect_to_resolved(Varlink
**ret
) {
54 _cleanup_(varlink_unrefp
) Varlink
*link
= NULL
;
57 r
= varlink_connect_address(&link
, "/run/systemd/resolve/io.systemd.Resolve");
61 r
= varlink_set_relative_timeout(link
, SD_RESOLVED_QUERY_TIMEOUT_USEC
);
65 *ret
= TAKE_PTR(link
);
69 static uint32_t ifindex_to_scopeid(int family
, const void *a
, int ifindex
) {
72 if (family
!= AF_INET6
|| ifindex
== 0)
75 /* Some apps can't deal with the scope ID attached to non-link-local addresses. Hence, let's suppress that. */
77 assert(sizeof(in6
) == FAMILY_ADDRESS_SIZE(AF_INET6
));
78 memcpy(&in6
, a
, sizeof(struct in6_addr
));
80 return in6_addr_is_link_local(&in6
) ? ifindex
: 0;
83 static int json_dispatch_ifindex(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
90 if (!json_variant_is_integer(variant
))
91 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an integer.", strna(name
));
93 t
= json_variant_integer(variant
);
95 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is out of bounds for an interface index.", strna(name
));
101 static int json_dispatch_family(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
102 int *family
= userdata
;
108 if (!json_variant_is_integer(variant
))
109 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an integer.", strna(name
));
111 t
= json_variant_integer(variant
);
112 if (t
< 0 || t
> INT_MAX
)
113 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not a valid family.", strna(name
));
119 typedef struct ResolveHostnameReply
{
120 JsonVariant
*addresses
;
123 } ResolveHostnameReply
;
125 static void resolve_hostname_reply_destroy(ResolveHostnameReply
*p
) {
128 json_variant_unref(p
->addresses
);
132 static const JsonDispatch resolve_hostname_reply_dispatch_table
[] = {
133 { "addresses", JSON_VARIANT_ARRAY
, json_dispatch_variant
, offsetof(ResolveHostnameReply
, addresses
), JSON_MANDATORY
},
134 { "name", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(ResolveHostnameReply
, name
), 0 },
135 { "flags", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(ResolveHostnameReply
, flags
), 0 },
139 typedef struct AddressParameters
{
142 union in_addr_union address
;
146 static int json_dispatch_address(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
147 AddressParameters
*p
= userdata
;
148 union in_addr_union buf
= {};
155 if (!json_variant_is_array(variant
))
156 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an array.", strna(name
));
158 n
= json_variant_elements(variant
);
159 if (!IN_SET(n
, 4, 16))
160 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is array of unexpected size.", strna(name
));
162 JSON_VARIANT_ARRAY_FOREACH(i
, variant
) {
165 if (!json_variant_is_integer(i
))
166 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "Element %zu of JSON field '%s' is not an integer.", k
, strna(name
));
168 b
= json_variant_integer(i
);
169 if (b
< 0 || b
> 0xff)
170 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "Element %zu of JSON field '%s' is out of range 0…255.", k
, strna(name
));
172 buf
.bytes
[k
++] = (uint8_t) b
;
181 static const JsonDispatch address_parameters_dispatch_table
[] = {
182 { "ifindex", JSON_VARIANT_INTEGER
, json_dispatch_ifindex
, offsetof(AddressParameters
, ifindex
), 0 },
183 { "family", JSON_VARIANT_INTEGER
, json_dispatch_family
, offsetof(AddressParameters
, family
), JSON_MANDATORY
},
184 { "address", JSON_VARIANT_ARRAY
, json_dispatch_address
, 0, JSON_MANDATORY
},
188 static uint64_t query_flags(void) {
192 /* Allow callers to turn off validation, when we resolve via nss-resolve */
194 r
= getenv_bool_secure("SYSTEMD_NSS_RESOLVE_VALIDATE");
195 if (r
< 0 && r
!= -ENXIO
)
196 log_debug_errno(r
, "Failed to parse $SYSTEMD_NSS_RESOLVE_VALIDATE value, ignoring.");
198 f
|= SD_RESOLVED_NO_VALIDATE
;
203 enum nss_status
_nss_resolve_gethostbyname4_r(
205 struct gaih_addrtuple
**pat
,
206 char *buffer
, size_t buflen
,
207 int *errnop
, int *h_errnop
,
210 _cleanup_(varlink_unrefp
) Varlink
*link
= NULL
;
211 _cleanup_(json_variant_unrefp
) JsonVariant
*cparams
= NULL
;
212 _cleanup_(resolve_hostname_reply_destroy
) ResolveHostnameReply p
= {};
213 JsonVariant
*rparams
, *entry
;
217 NSS_ENTRYPOINT_BEGIN
;
225 r
= connect_to_resolved(&link
);
229 r
= json_build(&cparams
, JSON_BUILD_OBJECT(
230 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name
)),
231 JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(query_flags()))));
235 /* Return NSS_STATUS_UNAVAIL when communication with systemd-resolved fails, allowing falling
236 * back to other nss modules. Treat all other error conditions as NOTFOUND. This includes
237 * DNSSEC errors and suchlike. (We don't use UNAVAIL in this case so that the nsswitch.conf
238 * configuration can distinguish such executed but negative replies from complete failure to
239 * talk to resolved). */
240 const char *error_id
;
241 r
= varlink_call(link
, "io.systemd.Resolve.ResolveHostname", cparams
, &rparams
, &error_id
, NULL
);
244 if (!isempty(error_id
)) {
245 if (!error_shall_fallback(error_id
))
250 r
= json_dispatch(rparams
, resolve_hostname_reply_dispatch_table
, NULL
, json_dispatch_flags
, &p
);
253 if (json_variant_is_blank_object(p
.addresses
))
256 size_t n_addresses
= 0;
257 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.addresses
) {
258 AddressParameters q
= {};
260 r
= json_dispatch(entry
, address_parameters_dispatch_table
, NULL
, json_dispatch_flags
, &q
);
264 if (!IN_SET(q
.family
, AF_INET
, AF_INET6
))
267 if (q
.address_size
!= FAMILY_ADDRESS_SIZE(q
.family
)) {
275 const char *canonical
= p
.name
?: name
;
276 size_t l
= strlen(canonical
);
277 size_t idx
, ms
= ALIGN(l
+1) + ALIGN(sizeof(struct gaih_addrtuple
)) * n_addresses
;
282 *h_errnop
= NETDB_INTERNAL
;
283 return NSS_STATUS_TRYAGAIN
;
286 /* First, append name */
287 char *r_name
= buffer
;
288 memcpy(r_name
, canonical
, l
+ 1);
291 /* Second, append addresses */
292 struct gaih_addrtuple
*r_tuple
= NULL
,
293 *r_tuple_first
= (struct gaih_addrtuple
*) (buffer
+ idx
);
295 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.addresses
) {
296 AddressParameters q
= {};
298 r
= json_dispatch(entry
, address_parameters_dispatch_table
, NULL
, json_dispatch_flags
, &q
);
302 if (!IN_SET(q
.family
, AF_INET
, AF_INET6
))
305 r_tuple
= (struct gaih_addrtuple
*) (buffer
+ idx
);
306 r_tuple
->next
= (struct gaih_addrtuple
*) ((char*) r_tuple
+ ALIGN(sizeof(struct gaih_addrtuple
)));
307 r_tuple
->name
= r_name
;
308 r_tuple
->family
= q
.family
;
309 r_tuple
->scopeid
= ifindex_to_scopeid(q
.family
, &q
.address
, q
.ifindex
);
310 memcpy(r_tuple
->addr
, &q
.address
, q
.address_size
);
312 idx
+= ALIGN(sizeof(struct gaih_addrtuple
));
315 assert(r_tuple
); /* We had at least one address, so r_tuple must be set */
316 r_tuple
->next
= NULL
; /* Override last next pointer */
321 **pat
= *r_tuple_first
;
323 *pat
= r_tuple_first
;
328 /* Explicitly reset both *h_errnop and h_errno to work around
329 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
330 *h_errnop
= NETDB_SUCCESS
;
333 return NSS_STATUS_SUCCESS
;
338 *h_errnop
= NO_RECOVERY
;
339 return NSS_STATUS_UNAVAIL
;
342 *h_errnop
= HOST_NOT_FOUND
;
343 return NSS_STATUS_NOTFOUND
;
346 enum nss_status
_nss_resolve_gethostbyname3_r(
349 struct hostent
*result
,
350 char *buffer
, size_t buflen
,
351 int *errnop
, int *h_errnop
,
355 _cleanup_(varlink_unrefp
) Varlink
*link
= NULL
;
356 _cleanup_(json_variant_unrefp
) JsonVariant
*cparams
= NULL
;
357 _cleanup_(resolve_hostname_reply_destroy
) ResolveHostnameReply p
= {};
358 JsonVariant
*rparams
, *entry
;
362 NSS_ENTRYPOINT_BEGIN
;
373 if (!IN_SET(af
, AF_INET
, AF_INET6
)) {
378 r
= connect_to_resolved(&link
);
382 r
= json_build(&cparams
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name
)),
383 JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(af
)),
384 JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(query_flags()))));
388 const char *error_id
;
389 r
= varlink_call(link
, "io.systemd.Resolve.ResolveHostname", cparams
, &rparams
, &error_id
, NULL
);
392 if (!isempty(error_id
)) {
393 if (!error_shall_fallback(error_id
))
398 r
= json_dispatch(rparams
, resolve_hostname_reply_dispatch_table
, NULL
, json_dispatch_flags
, &p
);
401 if (json_variant_is_blank_object(p
.addresses
))
404 size_t n_addresses
= 0;
405 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.addresses
) {
406 AddressParameters q
= {};
408 r
= json_dispatch(entry
, address_parameters_dispatch_table
, NULL
, json_dispatch_flags
, &q
);
412 if (!IN_SET(q
.family
, AF_INET
, AF_INET6
))
415 if (q
.address_size
!= FAMILY_ADDRESS_SIZE(q
.family
)) {
423 const char *canonical
= p
.name
?: name
;
425 size_t alen
= FAMILY_ADDRESS_SIZE(af
);
426 size_t l
= strlen(canonical
);
428 size_t idx
, ms
= ALIGN(l
+ 1) + n_addresses
* ALIGN(alen
) + (n_addresses
+ 2) * sizeof(char*);
433 *h_errnop
= NETDB_INTERNAL
;
434 return NSS_STATUS_TRYAGAIN
;
437 /* First, append name */
438 char *r_name
= buffer
;
439 memcpy(r_name
, canonical
, l
+1);
442 /* Second, create empty aliases array */
443 char *r_aliases
= buffer
+ idx
;
444 ((char**) r_aliases
)[0] = NULL
;
445 idx
+= sizeof(char*);
447 /* Third, append addresses */
448 char *r_addr
= buffer
+ idx
;
451 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.addresses
) {
452 AddressParameters q
= {};
454 r
= json_dispatch(entry
, address_parameters_dispatch_table
, NULL
, json_dispatch_flags
, &q
);
461 if (q
.address_size
!= alen
) {
466 memcpy(r_addr
+ i
*ALIGN(alen
), &q
.address
, alen
);
470 assert(i
== n_addresses
);
471 idx
+= n_addresses
* ALIGN(alen
);
473 /* Fourth, append address pointer array */
474 char *r_addr_list
= buffer
+ idx
;
475 for (i
= 0; i
< n_addresses
; i
++)
476 ((char**) r_addr_list
)[i
] = r_addr
+ i
*ALIGN(alen
);
478 ((char**) r_addr_list
)[i
] = NULL
;
479 idx
+= (n_addresses
+ 1) * sizeof(char*);
483 result
->h_name
= r_name
;
484 result
->h_aliases
= (char**) r_aliases
;
485 result
->h_addrtype
= af
;
486 result
->h_length
= alen
;
487 result
->h_addr_list
= (char**) r_addr_list
;
495 /* Explicitly reset both *h_errnop and h_errno to work around
496 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
497 *h_errnop
= NETDB_SUCCESS
;
500 return NSS_STATUS_SUCCESS
;
505 *h_errnop
= NO_RECOVERY
;
506 return NSS_STATUS_UNAVAIL
;
509 *h_errnop
= HOST_NOT_FOUND
;
510 return NSS_STATUS_NOTFOUND
;
513 typedef struct ResolveAddressReply
{
516 } ResolveAddressReply
;
518 static void resolve_address_reply_destroy(ResolveAddressReply
*p
) {
521 json_variant_unref(p
->names
);
524 static const JsonDispatch resolve_address_reply_dispatch_table
[] = {
525 { "names", JSON_VARIANT_ARRAY
, json_dispatch_variant
, offsetof(ResolveAddressReply
, names
), JSON_MANDATORY
},
526 { "flags", JSON_VARIANT_UNSIGNED
, json_dispatch_uint64
, offsetof(ResolveAddressReply
, flags
), 0 },
530 typedef struct NameParameters
{
535 static void name_parameters_destroy(NameParameters
*p
) {
541 static const JsonDispatch name_parameters_dispatch_table
[] = {
542 { "ifindex", JSON_VARIANT_INTEGER
, json_dispatch_ifindex
, offsetof(NameParameters
, ifindex
), 0 },
543 { "name", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(NameParameters
, name
), JSON_MANDATORY
},
547 enum nss_status
_nss_resolve_gethostbyaddr2_r(
548 const void* addr
, socklen_t len
,
550 struct hostent
*result
,
551 char *buffer
, size_t buflen
,
552 int *errnop
, int *h_errnop
,
555 _cleanup_(varlink_unrefp
) Varlink
*link
= NULL
;
556 _cleanup_(json_variant_unrefp
) JsonVariant
*cparams
= NULL
;
557 _cleanup_(resolve_address_reply_destroy
) ResolveAddressReply p
= {};
558 JsonVariant
*rparams
, *entry
;
562 NSS_ENTRYPOINT_BEGIN
;
570 if (!IN_SET(af
, AF_INET
, AF_INET6
)) {
572 *errnop
= EAFNOSUPPORT
;
574 return NSS_STATUS_UNAVAIL
;
577 if (len
!= FAMILY_ADDRESS_SIZE(af
)) {
582 r
= connect_to_resolved(&link
);
586 r
= json_build(&cparams
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(addr
, len
)),
587 JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(af
)),
588 JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(query_flags()))));
592 const char* error_id
;
593 r
= varlink_call(link
, "io.systemd.Resolve.ResolveAddress", cparams
, &rparams
, &error_id
, NULL
);
596 if (!isempty(error_id
)) {
597 if (!error_shall_fallback(error_id
))
602 r
= json_dispatch(rparams
, resolve_address_reply_dispatch_table
, NULL
, json_dispatch_flags
, &p
);
605 if (json_variant_is_blank_object(p
.names
))
610 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.names
) {
611 _cleanup_(name_parameters_destroy
) NameParameters q
= {};
613 r
= json_dispatch(entry
, name_parameters_dispatch_table
, NULL
, json_dispatch_flags
, &q
);
617 ms
+= ALIGN(strlen(q
.name
) + 1);
620 size_t n_names
= json_variant_elements(p
.names
);
621 ms
+= ALIGN(len
) + /* the address */
622 2 * sizeof(char*) + /* pointer to the address, plus trailing NULL */
623 n_names
* sizeof(char*); /* pointers to aliases, plus trailing NULL */
628 *h_errnop
= NETDB_INTERNAL
;
629 return NSS_STATUS_TRYAGAIN
;
632 /* First, place address */
633 char *r_addr
= buffer
;
634 memcpy(r_addr
, addr
, len
);
637 /* Second, place address list */
638 char *r_addr_list
= buffer
+ idx
;
639 ((char**) r_addr_list
)[0] = r_addr
;
640 ((char**) r_addr_list
)[1] = NULL
;
641 idx
+= sizeof(char*) * 2;
643 /* Third, reserve space for the aliases array, plus trailing NULL */
644 char *r_aliases
= buffer
+ idx
;
645 idx
+= sizeof(char*) * n_names
;
647 /* Fourth, place aliases */
648 char *r_name
= buffer
+ idx
;
651 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.names
) {
652 _cleanup_(name_parameters_destroy
) NameParameters q
= {};
654 r
= json_dispatch(entry
, name_parameters_dispatch_table
, NULL
, json_dispatch_flags
, &q
);
658 size_t l
= strlen(q
.name
);
659 char *z
= buffer
+ idx
;
660 memcpy(z
, q
.name
, l
+ 1);
663 ((char**) r_aliases
)[i
- 1] = z
;
668 ((char**) r_aliases
)[n_names
- 1] = NULL
;
672 result
->h_name
= r_name
;
673 result
->h_aliases
= (char**) r_aliases
;
674 result
->h_addrtype
= af
;
675 result
->h_length
= len
;
676 result
->h_addr_list
= (char**) r_addr_list
;
681 /* Explicitly reset both *h_errnop and h_errno to work around
682 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
683 *h_errnop
= NETDB_SUCCESS
;
686 return NSS_STATUS_SUCCESS
;
691 *h_errnop
= NO_RECOVERY
;
692 return NSS_STATUS_UNAVAIL
;
695 *h_errnop
= HOST_NOT_FOUND
;
696 return NSS_STATUS_NOTFOUND
;
699 NSS_GETHOSTBYNAME_FALLBACKS(resolve
);
700 NSS_GETHOSTBYADDR_FALLBACKS(resolve
);