/* SPDX-License-Identifier: LGPL-2.1+ */ /*** This file is part of systemd. Copyright 2016 Zbigniew Jędrzejewski-Szmek systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #include #include #include #include "log.h" #include "nss-util.h" #include "path-util.h" #include "string-util.h" #include "alloc-util.h" #include "in-addr-util.h" #include "hexdecoct.h" #include "af-list.h" #include "stdio-util.h" #include "strv.h" #include "errno-list.h" #include "hostname-util.h" #include "local-addresses.h" static const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len) { switch (status) { case NSS_STATUS_TRYAGAIN: return "NSS_STATUS_TRYAGAIN"; case NSS_STATUS_UNAVAIL: return "NSS_STATUS_UNAVAIL"; case NSS_STATUS_NOTFOUND: return "NSS_STATUS_NOTFOUND"; case NSS_STATUS_SUCCESS: return "NSS_STATUS_SUCCESS"; case NSS_STATUS_RETURN: return "NSS_STATUS_RETURN"; default: snprintf(buf, buf_len, "%i", status); return buf; } }; static const char* af_to_string(int family, char *buf, size_t buf_len) { const char *name; if (family == AF_UNSPEC) return "*"; name = af_to_name(family); if (name) return name; snprintf(buf, buf_len, "%i", family); return buf; } static void* open_handle(const char* dir, const char* module, int flags) { const char *path; void *handle; if (dir) { path = strjoina(dir, "/libnss_", module, ".so.2"); if (access(path, F_OK) < 0) path = strjoina(dir, "/.libs/libnss_", module, ".so.2"); } else path = strjoina("libnss_", module, ".so.2"); handle = dlopen(path, flags); if (!handle) log_error("Failed to load module %s: %s", module, dlerror()); return handle; } static int print_gaih_addrtuples(const struct gaih_addrtuple *tuples) { const struct gaih_addrtuple *it; int n = 0; for (it = tuples; it; it = it->next) { _cleanup_free_ char *a = NULL; union in_addr_union u; int r; char family_name[DECIMAL_STR_MAX(int)]; char ifname[IF_NAMESIZE]; memcpy(&u, it->addr, 16); r = in_addr_to_string(it->family, &u, &a); assert_se(IN_SET(r, 0, -EAFNOSUPPORT)); if (r == -EAFNOSUPPORT) assert_se((a = hexmem(it->addr, 16))); if (it->scopeid == 0) goto numerical_index; if (if_indextoname(it->scopeid, ifname) == NULL) { log_warning_errno(errno, "if_indextoname(%d) failed: %m", it->scopeid); numerical_index: xsprintf(ifname, "%i", it->scopeid); }; log_info(" \"%s\" %s %s %%%s", it->name, af_to_string(it->family, family_name, sizeof family_name), a, ifname); n ++; } return n; } static void print_struct_hostent(struct hostent *host, const char *canon) { char **s; log_info(" \"%s\"", host->h_name); STRV_FOREACH(s, host->h_aliases) log_info(" alias \"%s\"", *s); STRV_FOREACH(s, host->h_addr_list) { union in_addr_union u; _cleanup_free_ char *a = NULL; char family_name[DECIMAL_STR_MAX(int)]; int r; assert_se((unsigned) host->h_length == FAMILY_ADDRESS_SIZE(host->h_addrtype)); memcpy(&u, *s, host->h_length); r = in_addr_to_string(host->h_addrtype, &u, &a); assert_se(r == 0); log_info(" %s %s", af_to_string(host->h_addrtype, family_name, sizeof family_name), a); } if (canon) log_info(" canonical: \"%s\"", canon); } static void test_gethostbyname4_r(void *handle, const char *module, const char *name) { const char *fname; _nss_gethostbyname4_r_t f; char buffer[2000]; struct gaih_addrtuple *pat = NULL; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ int32_t ttl = INT32_MAX; /* nss-dns wants to return the lowest ttl, and will access this variable through *ttlp, so we need to set it to something. I'm not sure if this is a bug in nss-dns or not. */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; int n; fname = strjoina("_nss_", module, "_gethostbyname4_r"); f = dlsym(handle, fname); log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); assert_se(f); status = f(name, &pat, buffer, sizeof buffer, &errno1, &errno2, &ttl); if (status == NSS_STATUS_SUCCESS) { log_info("%s(\"%s\") → status=%s%-20spat=buffer+0x%tx errno=%d/%s h_errno=%d/%s ttl=%"PRIi32, fname, name, nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", pat ? (char*) pat - buffer : 0, errno1, errno_to_name(errno1) ?: "---", errno2, hstrerror(errno2), ttl); n = print_gaih_addrtuples(pat); } else { log_info("%s(\"%s\") → status=%s%-20spat=0x%p errno=%d/%s h_errno=%d/%s", fname, name, nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", pat, errno1, errno_to_name(errno1) ?: "---", errno2, hstrerror(errno2)); n = 0; } if (STR_IN_SET(module, "resolve", "mymachines") && status == NSS_STATUS_UNAVAIL) return; if (STR_IN_SET(module, "myhostname", "resolve") && streq(name, "localhost")) { assert_se(status == NSS_STATUS_SUCCESS); assert_se(n == 2); } } static void test_gethostbyname3_r(void *handle, const char *module, const char *name, int af) { const char *fname; _nss_gethostbyname3_r_t f; char buffer[2000]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ int32_t ttl = INT32_MAX; /* nss-dns wants to return the lowest ttl, and will access this variable through *ttlp, so we need to set it to something. I'm not sure if this is a bug in nss-dns or not. */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; struct hostent host; char *canon; char family_name[DECIMAL_STR_MAX(int)]; fname = strjoina("_nss_", module, "_gethostbyname3_r"); f = dlsym(handle, fname); log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); assert_se(f); status = f(name, af, &host, buffer, sizeof buffer, &errno1, &errno2, &ttl, &canon); log_info("%s(\"%s\", %s) → status=%s%-20serrno=%d/%s h_errno=%d/%s ttl=%"PRIi32, fname, name, af_to_string(af, family_name, sizeof family_name), nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", errno1, errno_to_name(errno1) ?: "---", errno2, hstrerror(errno2), ttl); if (status == NSS_STATUS_SUCCESS) print_struct_hostent(&host, canon); } static void test_gethostbyname2_r(void *handle, const char *module, const char *name, int af) { const char *fname; _nss_gethostbyname2_r_t f; char buffer[2000]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; struct hostent host; char family_name[DECIMAL_STR_MAX(int)]; fname = strjoina("_nss_", module, "_gethostbyname2_r"); f = dlsym(handle, fname); log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); assert_se(f); status = f(name, af, &host, buffer, sizeof buffer, &errno1, &errno2); log_info("%s(\"%s\", %s) → status=%s%-20serrno=%d/%s h_errno=%d/%s", fname, name, af_to_string(af, family_name, sizeof family_name), nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", errno1, errno_to_name(errno1) ?: "---", errno2, hstrerror(errno2)); if (status == NSS_STATUS_SUCCESS) print_struct_hostent(&host, NULL); } static void test_gethostbyname_r(void *handle, const char *module, const char *name) { const char *fname; _nss_gethostbyname_r_t f; char buffer[2000]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; struct hostent host; fname = strjoina("_nss_", module, "_gethostbyname_r"); f = dlsym(handle, fname); log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); assert_se(f); status = f(name, &host, buffer, sizeof buffer, &errno1, &errno2); log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s h_errno=%d/%s", fname, name, nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", errno1, errno_to_name(errno1) ?: "---", errno2, hstrerror(errno2)); if (status == NSS_STATUS_SUCCESS) print_struct_hostent(&host, NULL); } static void test_gethostbyaddr2_r(void *handle, const char *module, const void* addr, socklen_t len, int af) { const char *fname; _nss_gethostbyaddr2_r_t f; char buffer[2000]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; struct hostent host; int32_t ttl = INT32_MAX; _cleanup_free_ char *addr_pretty = NULL; fname = strjoina("_nss_", module, "_gethostbyaddr2_r"); f = dlsym(handle, fname); log_full_errno(f ? LOG_DEBUG : LOG_INFO, errno, "dlsym(0x%p, %s) → 0x%p: %m", handle, fname, f); if (!f) return; assert_se(in_addr_to_string(af, addr, &addr_pretty) >= 0); status = f(addr, len, af, &host, buffer, sizeof buffer, &errno1, &errno2, &ttl); log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s h_errno=%d/%s ttl=%"PRIi32, fname, addr_pretty, nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", errno1, errno_to_name(errno1) ?: "---", errno2, hstrerror(errno2), ttl); if (status == NSS_STATUS_SUCCESS) print_struct_hostent(&host, NULL); } static void test_gethostbyaddr_r(void *handle, const char *module, const void* addr, socklen_t len, int af) { const char *fname; _nss_gethostbyaddr_r_t f; char buffer[2000]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; struct hostent host; _cleanup_free_ char *addr_pretty = NULL; fname = strjoina("_nss_", module, "_gethostbyaddr_r"); f = dlsym(handle, fname); log_full_errno(f ? LOG_DEBUG : LOG_INFO, errno, "dlsym(0x%p, %s) → 0x%p: %m", handle, fname, f); if (!f) return; assert_se(in_addr_to_string(af, addr, &addr_pretty) >= 0); status = f(addr, len, af, &host, buffer, sizeof buffer, &errno1, &errno2); log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s h_errno=%d/%s", fname, addr_pretty, nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", errno1, errno_to_name(errno1) ?: "---", errno2, hstrerror(errno2)); if (status == NSS_STATUS_SUCCESS) print_struct_hostent(&host, NULL); } static void test_byname(void *handle, const char *module, const char *name) { test_gethostbyname4_r(handle, module, name); puts(""); test_gethostbyname3_r(handle, module, name, AF_INET); puts(""); test_gethostbyname3_r(handle, module, name, AF_INET6); puts(""); test_gethostbyname3_r(handle, module, name, AF_UNSPEC); puts(""); test_gethostbyname3_r(handle, module, name, AF_LOCAL); puts(""); test_gethostbyname2_r(handle, module, name, AF_INET); puts(""); test_gethostbyname2_r(handle, module, name, AF_INET6); puts(""); test_gethostbyname2_r(handle, module, name, AF_UNSPEC); puts(""); test_gethostbyname2_r(handle, module, name, AF_LOCAL); puts(""); test_gethostbyname_r(handle, module, name); puts(""); } static void test_byaddr(void *handle, const char *module, const void* addr, socklen_t len, int af) { test_gethostbyaddr2_r(handle, module, addr, len, af); puts(""); test_gethostbyaddr_r(handle, module, addr, len, af); puts(""); } static int make_addresses(struct local_address **addresses) { int n; size_t n_alloc; _cleanup_free_ struct local_address *addrs = NULL; n = local_addresses(NULL, 0, AF_UNSPEC, &addrs); if (n < 0) log_info_errno(n, "Failed to query local addresses: %m"); n_alloc = n; /* we _can_ do that */ if (!GREEDY_REALLOC(addrs, n_alloc, n + 3)) return log_oom(); addrs[n++] = (struct local_address) { .family = AF_INET, .address.in = { htobe32(0x7F000001) } }; addrs[n++] = (struct local_address) { .family = AF_INET, .address.in = { htobe32(0x7F000002) } }; addrs[n++] = (struct local_address) { .family = AF_INET6, .address.in6 = in6addr_loopback }; return 0; } static int test_one_module(const char* dir, const char *module, char **names, struct local_address *addresses, int n_addresses) { void *handle; char **name; int i; log_info("======== %s ========", module); handle = open_handle(streq(module, "dns") ? NULL : dir, module, RTLD_LAZY|RTLD_NODELETE); if (!handle) return -EINVAL; STRV_FOREACH(name, names) test_byname(handle, module, *name); for (i = 0; i < n_addresses; i++) test_byaddr(handle, module, &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family), addresses[i].family); log_info(" "); dlclose(handle); return 0; } static int parse_argv(int argc, char **argv, char ***the_modules, char ***the_names, struct local_address **the_addresses, int *n_addresses) { int r, n = 0; _cleanup_strv_free_ char **modules = NULL, **names = NULL; _cleanup_free_ struct local_address *addrs = NULL; size_t n_allocated = 0; if (argc > 1) modules = strv_new(argv[1], NULL); else modules = strv_new( #if ENABLE_MYHOSTNAME "myhostname", #endif #if ENABLE_RESOLVE "resolve", #endif #if ENABLE_MACHINED "mymachines", #endif "dns", NULL); if (!modules) return -ENOMEM; if (argc > 2) { char **name; int family; union in_addr_union address; STRV_FOREACH(name, argv + 2) { r = in_addr_from_string_auto(*name, &family, &address); if (r < 0) { /* assume this is a name */ r = strv_extend(&names, *name); if (r < 0) return r; } else { if (!GREEDY_REALLOC0(addrs, n_allocated, n + 1)) return -ENOMEM; addrs[n++] = (struct local_address) { .family = family, .address = address }; } } } else { _cleanup_free_ char *hostname; hostname = gethostname_malloc(); if (!hostname) return -ENOMEM; names = strv_new("localhost", "_gateway", "foo_no_such_host", hostname, NULL); if (!names) return -ENOMEM; n = make_addresses(&addrs); if (n < 0) return n; } *the_modules = modules; *the_names = names; modules = names = NULL; *the_addresses = addrs; *n_addresses = n; addrs = NULL; return 0; } int main(int argc, char **argv) { _cleanup_free_ char *dir = NULL; _cleanup_strv_free_ char **modules = NULL, **names = NULL; _cleanup_free_ struct local_address *addresses = NULL; int n_addresses = 0; char **module; int r; log_set_max_level(LOG_INFO); log_parse_environment(); r = parse_argv(argc, argv, &modules, &names, &addresses, &n_addresses); if (r < 0) { log_error_errno(r, "Failed to parse arguments: %m"); return EXIT_FAILURE; } dir = dirname_malloc(argv[0]); if (!dir) return EXIT_FAILURE; STRV_FOREACH(module, modules) { r = test_one_module(dir, *module, names, addresses, n_addresses); if (r < 0) return EXIT_FAILURE; } return EXIT_SUCCESS; }