1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
12 #include "errno-util.h"
13 #include "glyph-util.h"
14 #include "in-addr-util.h"
17 #include "resolved-def.h"
18 #include "signal-util.h"
19 #include "string-util.h"
23 static JsonDispatchFlags json_dispatch_flags
= JSON_ALLOW_EXTENSIONS
;
25 static void setup_logging(void) {
26 log_parse_environment_variables();
29 json_dispatch_flags
= JSON_LOG
;
32 static void setup_logging_once(void) {
33 static pthread_once_t once
= PTHREAD_ONCE_INIT
;
34 assert_se(pthread_once(&once
, setup_logging
) == 0);
37 #define NSS_ENTRYPOINT_BEGIN \
38 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); \
41 NSS_GETHOSTBYNAME_PROTOTYPES(resolve
);
42 NSS_GETHOSTBYADDR_PROTOTYPES(resolve
);
44 static bool error_shall_fallback(const char *error_id
) {
45 /* The Varlink errors where we shall signal "please fallback" back to the NSS stack, so that some
46 * fallback module can be loaded. (These are mostly all Varlink-internal errors, as apparently we
47 * then were unable to even do IPC with systemd-resolved.) */
48 return STR_IN_SET(error_id
,
49 VARLINK_ERROR_DISCONNECTED
,
50 VARLINK_ERROR_TIMEOUT
,
51 VARLINK_ERROR_PROTOCOL
,
52 VARLINK_ERROR_INTERFACE_NOT_FOUND
,
53 VARLINK_ERROR_METHOD_NOT_FOUND
,
54 VARLINK_ERROR_METHOD_NOT_IMPLEMENTED
);
57 static bool error_shall_try_again(const char *error_id
) {
58 /* The Varlink errors where we shall signal "can't answer now but might be able to later" back to the
59 * NSS stack. These are all errors that indicate lack of configuration or network problems. */
60 return STR_IN_SET(error_id
,
61 "io.systemd.Resolve.NoNameServers",
62 "io.systemd.Resolve.QueryTimedOut",
63 "io.systemd.Resolve.MaxAttemptsReached",
64 "io.systemd.Resolve.NetworkDown");
67 static int connect_to_resolved(Varlink
**ret
) {
68 _cleanup_(varlink_unrefp
) Varlink
*link
= NULL
;
71 r
= varlink_connect_address(&link
, "/run/systemd/resolve/io.systemd.Resolve");
75 r
= varlink_set_relative_timeout(link
, SD_RESOLVED_QUERY_TIMEOUT_USEC
);
79 *ret
= TAKE_PTR(link
);
83 static uint32_t ifindex_to_scopeid(int family
, const void *a
, int ifindex
) {
86 if (family
!= AF_INET6
|| ifindex
== 0)
89 /* Some apps can't deal with the scope ID attached to non-link-local addresses. Hence, let's suppress that. */
91 assert(sizeof(in6
) == FAMILY_ADDRESS_SIZE(AF_INET6
));
92 memcpy(&in6
, a
, sizeof(struct in6_addr
));
94 return in6_addr_is_link_local(&in6
) ? ifindex
: 0;
97 static int json_dispatch_ifindex(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
98 int *ifi
= ASSERT_PTR(userdata
);
103 if (!json_variant_is_integer(variant
))
104 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an integer.", strna(name
));
106 t
= json_variant_integer(variant
);
108 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is out of bounds for an interface index.", strna(name
));
114 static int json_dispatch_family(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
115 int *family
= ASSERT_PTR(userdata
);
120 if (!json_variant_is_integer(variant
))
121 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an integer.", strna(name
));
123 t
= json_variant_integer(variant
);
124 if (t
< 0 || t
> INT_MAX
)
125 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not a valid family.", strna(name
));
131 typedef struct ResolveHostnameReply
{
132 JsonVariant
*addresses
;
135 } ResolveHostnameReply
;
137 static void resolve_hostname_reply_destroy(ResolveHostnameReply
*p
) {
140 json_variant_unref(p
->addresses
);
144 static const JsonDispatch resolve_hostname_reply_dispatch_table
[] = {
145 { "addresses", JSON_VARIANT_ARRAY
, json_dispatch_variant
, offsetof(ResolveHostnameReply
, addresses
), JSON_MANDATORY
},
146 { "name", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(ResolveHostnameReply
, name
), 0 },
147 { "flags", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_uint64
, offsetof(ResolveHostnameReply
, flags
), 0 },
151 typedef struct AddressParameters
{
154 union in_addr_union address
;
158 static int json_dispatch_address(const char *name
, JsonVariant
*variant
, JsonDispatchFlags flags
, void *userdata
) {
159 AddressParameters
*p
= ASSERT_PTR(userdata
);
160 union in_addr_union buf
= {};
166 if (!json_variant_is_array(variant
))
167 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is not an array.", strna(name
));
169 n
= json_variant_elements(variant
);
170 if (!IN_SET(n
, 4, 16))
171 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "JSON field '%s' is array of unexpected size.", strna(name
));
173 JSON_VARIANT_ARRAY_FOREACH(i
, variant
) {
176 if (!json_variant_is_integer(i
))
177 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
), "Element %zu of JSON field '%s' is not an integer.", k
, strna(name
));
179 b
= json_variant_integer(i
);
180 if (b
< 0 || b
> 0xff)
181 return json_log(variant
, flags
, SYNTHETIC_ERRNO(EINVAL
),
182 "Element %zu of JSON field '%s' is out of range 0%s255.",
183 k
, strna(name
), special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
185 buf
.bytes
[k
++] = (uint8_t) b
;
194 static const JsonDispatch address_parameters_dispatch_table
[] = {
195 { "ifindex", JSON_VARIANT_INTEGER
, json_dispatch_ifindex
, offsetof(AddressParameters
, ifindex
), 0 },
196 { "family", JSON_VARIANT_INTEGER
, json_dispatch_family
, offsetof(AddressParameters
, family
), JSON_MANDATORY
},
197 { "address", JSON_VARIANT_ARRAY
, json_dispatch_address
, 0, JSON_MANDATORY
},
201 static uint64_t query_flag(
208 r
= secure_getenv_bool(name
);
210 return r
== value
? flag
: 0;
212 log_debug_errno(r
, "Failed to parse $%s, ignoring.", name
);
216 static uint64_t query_flags(void) {
217 /* Allow callers to turn off validation, synthetization, caching, etc., when we resolve via
219 return query_flag("SYSTEMD_NSS_RESOLVE_VALIDATE", 0, SD_RESOLVED_NO_VALIDATE
) |
220 query_flag("SYSTEMD_NSS_RESOLVE_SYNTHESIZE", 0, SD_RESOLVED_NO_SYNTHESIZE
) |
221 query_flag("SYSTEMD_NSS_RESOLVE_CACHE", 0, SD_RESOLVED_NO_CACHE
) |
222 query_flag("SYSTEMD_NSS_RESOLVE_ZONE", 0, SD_RESOLVED_NO_ZONE
) |
223 query_flag("SYSTEMD_NSS_RESOLVE_TRUST_ANCHOR", 0, SD_RESOLVED_NO_TRUST_ANCHOR
) |
224 query_flag("SYSTEMD_NSS_RESOLVE_NETWORK", 0, SD_RESOLVED_NO_NETWORK
);
227 enum nss_status
_nss_resolve_gethostbyname4_r(
229 struct gaih_addrtuple
**pat
,
230 char *buffer
, size_t buflen
,
231 int *errnop
, int *h_errnop
,
234 _cleanup_(varlink_unrefp
) Varlink
*link
= NULL
;
235 _cleanup_(json_variant_unrefp
) JsonVariant
*cparams
= NULL
;
236 _cleanup_(resolve_hostname_reply_destroy
) ResolveHostnameReply p
= {};
237 JsonVariant
*rparams
, *entry
;
241 NSS_ENTRYPOINT_BEGIN
;
249 r
= connect_to_resolved(&link
);
253 r
= json_build(&cparams
, JSON_BUILD_OBJECT(
254 JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name
)),
255 JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(query_flags()))));
259 /* Return NSS_STATUS_UNAVAIL when communication with systemd-resolved fails, allowing falling
260 * back to other nss modules. Treat all other error conditions as NOTFOUND. This includes
261 * DNSSEC errors and suchlike. (We don't use UNAVAIL in this case so that the nsswitch.conf
262 * configuration can distinguish such executed but negative replies from complete failure to
263 * talk to resolved). */
264 const char *error_id
;
265 r
= varlink_call(link
, "io.systemd.Resolve.ResolveHostname", cparams
, &rparams
, &error_id
);
268 if (!isempty(error_id
)) {
269 if (error_shall_try_again(error_id
))
271 if (error_shall_fallback(error_id
))
273 if (streq(error_id
, "io.systemd.Resolve.NoSuchResourceRecord"))
278 r
= json_dispatch(rparams
, resolve_hostname_reply_dispatch_table
, json_dispatch_flags
, &p
);
281 if (json_variant_is_blank_object(p
.addresses
))
284 size_t n_addresses
= 0;
285 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.addresses
) {
286 AddressParameters q
= {};
288 r
= json_dispatch(entry
, address_parameters_dispatch_table
, json_dispatch_flags
, &q
);
292 if (!IN_SET(q
.family
, AF_INET
, AF_INET6
))
295 if (q
.address_size
!= FAMILY_ADDRESS_SIZE(q
.family
)) {
303 const char *canonical
= p
.name
?: name
;
304 size_t l
= strlen(canonical
);
305 size_t idx
, ms
= ALIGN(l
+1) + ALIGN(sizeof(struct gaih_addrtuple
)) * n_addresses
;
310 *h_errnop
= NETDB_INTERNAL
;
311 return NSS_STATUS_TRYAGAIN
;
314 /* First, append name */
315 char *r_name
= buffer
;
316 memcpy(r_name
, canonical
, l
+ 1);
319 /* Second, append addresses */
320 struct gaih_addrtuple
*r_tuple
= NULL
,
321 *r_tuple_first
= (struct gaih_addrtuple
*) (buffer
+ idx
);
323 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.addresses
) {
324 AddressParameters q
= {};
326 r
= json_dispatch(entry
, address_parameters_dispatch_table
, json_dispatch_flags
, &q
);
330 if (!IN_SET(q
.family
, AF_INET
, AF_INET6
))
333 r_tuple
= (struct gaih_addrtuple
*) (buffer
+ idx
);
334 r_tuple
->next
= (struct gaih_addrtuple
*) ((char*) r_tuple
+ ALIGN(sizeof(struct gaih_addrtuple
)));
335 r_tuple
->name
= r_name
;
336 r_tuple
->family
= q
.family
;
337 r_tuple
->scopeid
= ifindex_to_scopeid(q
.family
, &q
.address
, q
.ifindex
);
338 memcpy(r_tuple
->addr
, &q
.address
, q
.address_size
);
340 idx
+= ALIGN(sizeof(struct gaih_addrtuple
));
343 assert(r_tuple
); /* We had at least one address, so r_tuple must be set */
344 r_tuple
->next
= NULL
; /* Override last next pointer */
349 **pat
= *r_tuple_first
;
351 *pat
= r_tuple_first
;
356 /* Explicitly reset both *h_errnop and h_errno to work around
357 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
358 *h_errnop
= NETDB_SUCCESS
;
361 return NSS_STATUS_SUCCESS
;
366 *h_errnop
= NO_RECOVERY
;
367 return NSS_STATUS_UNAVAIL
;
370 *h_errnop
= HOST_NOT_FOUND
;
371 return NSS_STATUS_NOTFOUND
;
375 return NSS_STATUS_NOTFOUND
;
380 *h_errnop
= TRY_AGAIN
;
381 return NSS_STATUS_TRYAGAIN
;
384 enum nss_status
_nss_resolve_gethostbyname3_r(
387 struct hostent
*result
,
388 char *buffer
, size_t buflen
,
389 int *errnop
, int *h_errnop
,
393 _cleanup_(varlink_unrefp
) Varlink
*link
= NULL
;
394 _cleanup_(json_variant_unrefp
) JsonVariant
*cparams
= NULL
;
395 _cleanup_(resolve_hostname_reply_destroy
) ResolveHostnameReply p
= {};
396 JsonVariant
*rparams
, *entry
;
400 NSS_ENTRYPOINT_BEGIN
;
411 if (!IN_SET(af
, AF_INET
, AF_INET6
)) {
416 r
= connect_to_resolved(&link
);
420 r
= json_build(&cparams
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name
)),
421 JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(af
)),
422 JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(query_flags()))));
426 const char *error_id
;
427 r
= varlink_call(link
, "io.systemd.Resolve.ResolveHostname", cparams
, &rparams
, &error_id
);
430 if (!isempty(error_id
)) {
431 if (error_shall_try_again(error_id
))
433 if (error_shall_fallback(error_id
))
435 if (streq(error_id
, "io.systemd.Resolve.NoSuchResourceRecord"))
440 r
= json_dispatch(rparams
, resolve_hostname_reply_dispatch_table
, json_dispatch_flags
, &p
);
443 if (json_variant_is_blank_object(p
.addresses
))
446 size_t n_addresses
= 0;
447 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.addresses
) {
448 AddressParameters q
= {};
450 r
= json_dispatch(entry
, address_parameters_dispatch_table
, json_dispatch_flags
, &q
);
454 if (!IN_SET(q
.family
, AF_INET
, AF_INET6
))
457 if (q
.address_size
!= FAMILY_ADDRESS_SIZE(q
.family
)) {
465 const char *canonical
= p
.name
?: name
;
467 size_t alen
= FAMILY_ADDRESS_SIZE(af
);
468 size_t l
= strlen(canonical
);
470 size_t idx
, ms
= ALIGN(l
+ 1) + n_addresses
* ALIGN(alen
) + (n_addresses
+ 2) * sizeof(char*);
475 *h_errnop
= NETDB_INTERNAL
;
476 return NSS_STATUS_TRYAGAIN
;
479 /* First, append name */
480 char *r_name
= buffer
;
481 memcpy(r_name
, canonical
, l
+1);
484 /* Second, create empty aliases array */
485 char *r_aliases
= buffer
+ idx
;
486 ((char**) r_aliases
)[0] = NULL
;
487 idx
+= sizeof(char*);
489 /* Third, append addresses */
490 char *r_addr
= buffer
+ idx
;
493 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.addresses
) {
494 AddressParameters q
= {};
496 r
= json_dispatch(entry
, address_parameters_dispatch_table
, json_dispatch_flags
, &q
);
503 if (q
.address_size
!= alen
) {
508 memcpy(r_addr
+ i
*ALIGN(alen
), &q
.address
, alen
);
512 assert(i
== n_addresses
);
513 idx
+= n_addresses
* ALIGN(alen
);
515 /* Fourth, append address pointer array */
516 char *r_addr_list
= buffer
+ idx
;
517 for (i
= 0; i
< n_addresses
; i
++)
518 ((char**) r_addr_list
)[i
] = r_addr
+ i
*ALIGN(alen
);
520 ((char**) r_addr_list
)[i
] = NULL
;
521 idx
+= (n_addresses
+ 1) * sizeof(char*);
525 result
->h_name
= r_name
;
526 result
->h_aliases
= (char**) r_aliases
;
527 result
->h_addrtype
= af
;
528 result
->h_length
= alen
;
529 result
->h_addr_list
= (char**) r_addr_list
;
537 /* Explicitly reset both *h_errnop and h_errno to work around
538 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
539 *h_errnop
= NETDB_SUCCESS
;
542 return NSS_STATUS_SUCCESS
;
547 *h_errnop
= NO_RECOVERY
;
548 return NSS_STATUS_UNAVAIL
;
551 *h_errnop
= HOST_NOT_FOUND
;
552 return NSS_STATUS_NOTFOUND
;
556 return NSS_STATUS_NOTFOUND
;
561 *h_errnop
= TRY_AGAIN
;
562 return NSS_STATUS_TRYAGAIN
;
565 typedef struct ResolveAddressReply
{
568 } ResolveAddressReply
;
570 static void resolve_address_reply_destroy(ResolveAddressReply
*p
) {
573 json_variant_unref(p
->names
);
576 static const JsonDispatch resolve_address_reply_dispatch_table
[] = {
577 { "names", JSON_VARIANT_ARRAY
, json_dispatch_variant
, offsetof(ResolveAddressReply
, names
), JSON_MANDATORY
},
578 { "flags", _JSON_VARIANT_TYPE_INVALID
, json_dispatch_uint64
, offsetof(ResolveAddressReply
, flags
), 0 },
582 typedef struct NameParameters
{
587 static void name_parameters_destroy(NameParameters
*p
) {
593 static const JsonDispatch name_parameters_dispatch_table
[] = {
594 { "ifindex", JSON_VARIANT_INTEGER
, json_dispatch_ifindex
, offsetof(NameParameters
, ifindex
), 0 },
595 { "name", JSON_VARIANT_STRING
, json_dispatch_string
, offsetof(NameParameters
, name
), JSON_MANDATORY
},
599 enum nss_status
_nss_resolve_gethostbyaddr2_r(
600 const void* addr
, socklen_t len
,
602 struct hostent
*result
,
603 char *buffer
, size_t buflen
,
604 int *errnop
, int *h_errnop
,
607 _cleanup_(varlink_unrefp
) Varlink
*link
= NULL
;
608 _cleanup_(json_variant_unrefp
) JsonVariant
*cparams
= NULL
;
609 _cleanup_(resolve_address_reply_destroy
) ResolveAddressReply p
= {};
610 JsonVariant
*rparams
, *entry
;
614 NSS_ENTRYPOINT_BEGIN
;
622 if (!IN_SET(af
, AF_INET
, AF_INET6
)) {
624 *errnop
= EAFNOSUPPORT
;
626 return NSS_STATUS_UNAVAIL
;
629 if (len
!= FAMILY_ADDRESS_SIZE(af
)) {
634 r
= connect_to_resolved(&link
);
638 r
= json_build(&cparams
, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(addr
, len
)),
639 JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(af
)),
640 JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(query_flags()))));
644 const char* error_id
;
645 r
= varlink_call(link
, "io.systemd.Resolve.ResolveAddress", cparams
, &rparams
, &error_id
);
648 if (!isempty(error_id
)) {
649 if (error_shall_try_again(error_id
))
651 if (error_shall_fallback(error_id
))
656 r
= json_dispatch(rparams
, resolve_address_reply_dispatch_table
, json_dispatch_flags
, &p
);
659 if (json_variant_is_blank_object(p
.names
))
664 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.names
) {
665 _cleanup_(name_parameters_destroy
) NameParameters q
= {};
667 r
= json_dispatch(entry
, name_parameters_dispatch_table
, json_dispatch_flags
, &q
);
671 ms
+= ALIGN(strlen(q
.name
) + 1);
674 size_t n_names
= json_variant_elements(p
.names
);
675 ms
+= ALIGN(len
) + /* the address */
676 2 * sizeof(char*) + /* pointer to the address, plus trailing NULL */
677 n_names
* sizeof(char*); /* pointers to aliases, plus trailing NULL */
682 *h_errnop
= NETDB_INTERNAL
;
683 return NSS_STATUS_TRYAGAIN
;
686 /* First, place address */
687 char *r_addr
= buffer
;
688 memcpy(r_addr
, addr
, len
);
691 /* Second, place address list */
692 char *r_addr_list
= buffer
+ idx
;
693 ((char**) r_addr_list
)[0] = r_addr
;
694 ((char**) r_addr_list
)[1] = NULL
;
695 idx
+= sizeof(char*) * 2;
697 /* Third, reserve space for the aliases array, plus trailing NULL */
698 char *r_aliases
= buffer
+ idx
;
699 idx
+= sizeof(char*) * n_names
;
701 /* Fourth, place aliases */
702 char *r_name
= buffer
+ idx
;
705 JSON_VARIANT_ARRAY_FOREACH(entry
, p
.names
) {
706 _cleanup_(name_parameters_destroy
) NameParameters q
= {};
708 r
= json_dispatch(entry
, name_parameters_dispatch_table
, json_dispatch_flags
, &q
);
712 size_t l
= strlen(q
.name
);
713 char *z
= buffer
+ idx
;
714 memcpy(z
, q
.name
, l
+ 1);
717 ((char**) r_aliases
)[i
- 1] = z
;
722 ((char**) r_aliases
)[n_names
- 1] = NULL
;
726 result
->h_name
= r_name
;
727 result
->h_aliases
= (char**) r_aliases
;
728 result
->h_addrtype
= af
;
729 result
->h_length
= len
;
730 result
->h_addr_list
= (char**) r_addr_list
;
735 /* Explicitly reset both *h_errnop and h_errno to work around
736 * https://bugzilla.redhat.com/show_bug.cgi?id=1125975 */
737 *h_errnop
= NETDB_SUCCESS
;
740 return NSS_STATUS_SUCCESS
;
745 *h_errnop
= NO_RECOVERY
;
746 return NSS_STATUS_UNAVAIL
;
749 *h_errnop
= HOST_NOT_FOUND
;
750 return NSS_STATUS_NOTFOUND
;
755 *h_errnop
= TRY_AGAIN
;
756 return NSS_STATUS_TRYAGAIN
;
759 NSS_GETHOSTBYNAME_FALLBACKS(resolve
);
760 NSS_GETHOSTBYADDR_FALLBACKS(resolve
);