prefixed with `:` in which case the kernel command line option takes
precedence, if it is specified as well.
-installed systemd tests:
-
-* `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if
- a test executable is moved to an arbitrary location.
-
`nss-systemd`:
* `$SYSTEMD_NSS_BYPASS_SYNTHETIC=1` — if set, `nss-systemd` won't synthesize
* `$SYSTEMD_SYSVRCND_PATH` — Controls where `systemd-sysv-generator` looks for
SysV init script runlevel link farms.
+systemd tests:
+
+* `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if
+ a test executable is moved to an arbitrary location.
+
+* `$SYSTEMD_TEST_NSS_BUFSIZE` — size of scratch buffers for "reentrant"
+ functions exported by the nss modules.
+
fuzzers:
* `$SYSTEMD_FUZZ_OUTPUT` — A boolean that specifies whether to write output to
struct hostent *host,
char *buffer, size_t buflen,
int *errnop, int *h_errnop);
+
+typedef enum nss_status (*_nss_getpwnam_r_t)(
+ const char *name,
+ struct passwd *pwd,
+ char *buffer, size_t buflen,
+ int *errnop);
+typedef enum nss_status (*_nss_getpwuid_r_t)(
+ uid_t uid,
+ struct passwd *pwd,
+ char *buffer, size_t buflen,
+ int *errnop);
+
+typedef enum nss_status (*_nss_getgrnam_r_t)(
+ const char *name,
+ struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop);
+typedef enum nss_status (*_nss_getgrgid_r_t)(
+ gid_t gid,
+ struct group *gr,
+ char *buffer, size_t buflen,
+ int *errnop);
[['src/test/test-gcrypt-util.c'],
[], [], [], 'HAVE_GCRYPT'],
- [['src/test/test-nss.c'],
+ [['src/test/test-nss-hosts.c',
+ 'src/test/nss-test-util.c',
+ 'src/test/nss-test-util.h'],
+ [],
+ [libdl],
+ [], 'ENABLE_NSS', 'manual'],
+
+ [['src/test/test-nss-users.c',
+ 'src/test/nss-test-util.c',
+ 'src/test/nss-test-util.h'],
[],
[libdl],
[], 'ENABLE_NSS', 'manual'],
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "nss-test-util.h"
+#include "string-util.h"
+
+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;
+ }
+};
+
+void* nss_open_handle(const char *dir, const char *module, int flags) {
+ const char *path = NULL;
+ void *handle;
+
+ if (dir)
+ path = strjoina(dir, "/libnss_", module, ".so.2");
+ if (!path || access(path, F_OK) < 0)
+ path = strjoina("libnss_", module, ".so.2");
+
+ log_debug("Using %s", path);
+ handle = dlopen(path, flags);
+ if (!handle)
+ log_error("Failed to load module %s: %s", module, dlerror());
+ return handle;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <nss.h>
+#include <stdint.h>
+
+const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len);
+void* nss_open_handle(const char *dir, const char *module, int flags);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include <dlfcn.h>
#include <net/if.h>
#include <stdlib.h>
#include <unistd.h>
#include "af-list.h"
#include "alloc-util.h"
+#include "dlfcn-util.h"
#include "errno-list.h"
#include "format-util.h"
#include "hexdecoct.h"
#include "local-addresses.h"
#include "log.h"
#include "main-func.h"
+#include "nss-test-util.h"
#include "nss-util.h"
+#include "parse-util.h"
#include "path-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "tests.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 size_t arg_bufsize = 1024;
static const char* af_to_string(int family, char *buf, size_t buf_len) {
const char *name;
return buf;
}
-static void* open_handle(const char *dir, const char *module, int flags) {
- const char *path = NULL;
- void *handle;
-
- if (dir)
- path = strjoina(dir, "/libnss_", module, ".so.2");
- if (!path || access(path, F_OK) < 0)
- path = strjoina("libnss_", module, ".so.2");
-
- log_debug("Using %s", path);
- 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) {
int n = 0;
static void test_gethostbyname4_r(void *handle, const char *module, const char *name) {
const char *fname;
_nss_gethostbyname4_r_t f;
- char buffer[2000];
+ char buffer[arg_bufsize];
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,
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];
+ char buffer[arg_bufsize];
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,
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];
+ char buffer[arg_bufsize];
int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
enum nss_status status;
char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
static void test_gethostbyname_r(void *handle, const char *module, const char *name) {
const char *fname;
_nss_gethostbyname_r_t f;
- char buffer[2000];
+ char buffer[arg_bufsize];
int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
enum nss_status status;
char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
const char *fname;
_nss_gethostbyaddr2_r_t f;
- char buffer[2000];
+ char buffer[arg_bufsize];
int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
enum nss_status status;
char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
const char *fname;
_nss_gethostbyaddr_r_t f;
- char buffer[2000];
+ char buffer[arg_bufsize];
int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */
enum nss_status status;
char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
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();
+ assert_se(GREEDY_REALLOC(addrs, n_alloc, n + 3));
addrs[n++] = (struct local_address) { .family = AF_INET,
.address.in = { htobe32(0x7F000001) } };
char **names,
struct local_address *addresses,
int n_addresses) {
- void *handle;
- char **name;
log_info("======== %s ========", module);
- handle = open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE);
+ _cleanup_(dlclosep) void *handle = nss_open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE);
if (!handle)
return -EINVAL;
+ char **name;
STRV_FOREACH(name, names)
test_byname(handle, module, *name);
addresses[i].family);
log_info(" ");
- dlclose(handle);
return 0;
}
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;
+ const char *p;
+ int r, n = 0;
+
+ p = getenv("SYSTEMD_TEST_NSS_BUFSIZE");
+ if (p) {
+ r = safe_atozu(p, &arg_bufsize);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse $SYSTEMD_TEST_NSS_BUFSIZE");
+ }
if (argc > 1)
modules = strv_new(argv[1]);
"mymachines",
#endif
"dns");
- if (!modules)
- return -ENOMEM;
+ assert_se(modules);
if (argc > 2) {
char **name;
if (r < 0)
return r;
} else {
- if (!GREEDY_REALLOC0(addrs, n_allocated, n + 1))
- return -ENOMEM;
+ assert_se(GREEDY_REALLOC0(addrs, n_allocated, n + 1));
addrs[n++] = (struct local_address) { .family = family,
.address = address };
}
} else {
_cleanup_free_ char *hostname;
+ assert_se(hostname = gethostname_malloc());
- hostname = gethostname_malloc();
- if (!hostname)
- return -ENOMEM;
-
- names = strv_new("localhost", "_gateway", "foo_no_such_host", hostname);
- if (!names)
- return -ENOMEM;
+ assert_se(names = strv_new("localhost", "_gateway", "foo_no_such_host", hostname));
n = make_addresses(&addrs);
- if (n < 0)
- return n;
+ assert_se(n >= 0);
}
- *the_modules = modules;
- *the_names = names;
- modules = names = NULL;
- *the_addresses = addrs;
+ *the_modules = TAKE_PTR(modules);
+ *the_names = TAKE_PTR(names);
+ *the_addresses = TAKE_PTR(addrs);
*n_addresses = n;
- addrs = NULL;
return 0;
}
test_setup_logging(LOG_INFO);
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;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse arguments: %m");
- dir = dirname_malloc(argv[0]);
- if (!dir)
- return log_oom();
+ assert_se(path_extract_directory(argv[0], &dir) >= 0);
STRV_FOREACH(module, modules) {
r = test_one_module(dir, *module, names, addresses, n_addresses);
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <pwd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "dlfcn-util.h"
+#include "errno-list.h"
+#include "format-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "nss-test-util.h"
+#include "nss-util.h"
+#include "path-util.h"
+#include "parse-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "tests.h"
+#include "user-util.h"
+
+static size_t arg_bufsize = 1024;
+
+static void print_struct_passwd(const struct passwd *pwd) {
+ log_info(" \"%s\" / "UID_FMT":"GID_FMT,
+ pwd->pw_name, pwd->pw_uid, pwd->pw_gid);
+ log_info(" passwd=\"%s\"", pwd->pw_passwd);
+ log_info(" gecos=\"%s\"", pwd->pw_gecos);
+ log_info(" dir=\"%s\"", pwd->pw_dir);
+ log_info(" shell=\"%s\"", pwd->pw_shell);
+}
+
+static void print_struct_group(const struct group *gr) {
+ _cleanup_free_ char *members = NULL;
+
+ log_info(" \"%s\" / "GID_FMT,
+ gr->gr_name, gr->gr_gid);
+ log_info(" passwd=\"%s\"", gr->gr_passwd);
+
+ assert_se(members = strv_join(gr->gr_mem, ", "));
+ // FIXME: use shell_maybe_quote(SHELL_ESCAPE_EMPTY) when it becomes available
+ log_info(" members=%s", members);
+}
+
+static void test_getpwnam_r(void *handle, const char *module, const char *name) {
+ const char *fname;
+ _nss_getpwnam_r_t f;
+ char buffer[arg_bufsize];
+ int errno1 = 999; /* nss-dns doesn't set those */
+ enum nss_status status;
+ char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
+ struct passwd pwd;
+
+ fname = strjoina("_nss_", module, "_getpwnam_r");
+ f = dlsym(handle, fname);
+ log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
+ if (!f) {
+ log_info("%s not defined", fname);
+ return;
+ }
+
+ status = f(name, &pwd, buffer, sizeof buffer, &errno1);
+ log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s",
+ fname, name,
+ nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
+ errno1, errno_to_name(errno1) ?: "---");
+ if (status == NSS_STATUS_SUCCESS)
+ print_struct_passwd(&pwd);
+}
+
+static void test_getgrnam_r(void *handle, const char *module, const char *name) {
+ const char *fname;
+ _nss_getgrnam_r_t f;
+ char buffer[arg_bufsize];
+ int errno1 = 999; /* nss-dns doesn't set those */
+ enum nss_status status;
+ char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
+ struct group gr;
+
+ fname = strjoina("_nss_", module, "_getgrnam_r");
+ f = dlsym(handle, fname);
+ log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
+ if (!f) {
+ log_info("%s not defined", fname);
+ return;
+ }
+
+ status = f(name, &gr, buffer, sizeof buffer, &errno1);
+ log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s",
+ fname, name,
+ nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
+ errno1, errno_to_name(errno1) ?: "---");
+ if (status == NSS_STATUS_SUCCESS)
+ print_struct_group(&gr);
+}
+
+static void test_getpwuid_r(void *handle, const char *module, uid_t uid) {
+ const char *fname;
+ _nss_getpwuid_r_t f;
+ char buffer[arg_bufsize];
+ int errno1 = 999; /* nss-dns doesn't set those */
+ enum nss_status status;
+ char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
+ struct passwd pwd;
+
+ fname = strjoina("_nss_", module, "_getpwuid_r");
+ f = dlsym(handle, fname);
+ log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
+ if (!f) {
+ log_info("%s not defined", fname);
+ return;
+ }
+
+ status = f(uid, &pwd, buffer, sizeof buffer, &errno1);
+ log_info("%s("UID_FMT") → status=%s%-20serrno=%d/%s",
+ fname, uid,
+ nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
+ errno1, errno_to_name(errno1) ?: "---");
+ if (status == NSS_STATUS_SUCCESS)
+ print_struct_passwd(&pwd);
+}
+
+static void test_getgrgid_r(void *handle, const char *module, gid_t gid) {
+ const char *fname;
+ _nss_getgrgid_r_t f;
+ char buffer[arg_bufsize];
+ int errno1 = 999; /* nss-dns doesn't set those */
+ enum nss_status status;
+ char pretty_status[DECIMAL_STR_MAX(enum nss_status)];
+ struct group gr;
+
+ fname = strjoina("_nss_", module, "_getgrgid_r");
+ f = dlsym(handle, fname);
+ log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f);
+ if (!f) {
+ log_info("%s not defined", fname);
+ return;
+ }
+
+ status = f(gid, &gr, buffer, sizeof buffer, &errno1);
+ log_info("%s("GID_FMT") → status=%s%-20serrno=%d/%s",
+ fname, gid,
+ nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n",
+ errno1, errno_to_name(errno1) ?: "---");
+ if (status == NSS_STATUS_SUCCESS)
+ print_struct_group(&gr);
+}
+
+static void test_byname(void *handle, const char *module, const char *name) {
+ test_getpwnam_r(handle, module, name);
+ test_getgrnam_r(handle, module, name);
+ puts("");
+}
+
+static void test_byuid(void *handle, const char *module, uid_t uid) {
+ test_getpwuid_r(handle, module, uid);
+ test_getgrgid_r(handle, module, uid);
+ puts("");
+}
+
+static int test_one_module(const char *dir,
+ const char *module,
+ char **names) {
+
+ log_info("======== %s ========", module);
+
+ _cleanup_(dlclosep) void *handle = nss_open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE);
+ if (!handle)
+ return -EINVAL;
+
+ char **name;
+ STRV_FOREACH(name, names)
+ test_byname(handle, module, *name);
+
+ STRV_FOREACH(name, names) {
+ uid_t uid;
+
+ assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+ /* We use safe_atou32 because we don't want to refuse invalid uids. */
+ if (safe_atou32(*name, &uid) < 0)
+ continue;
+
+ test_byuid(handle, module, uid);
+ }
+
+ log_info(" ");
+ return 0;
+}
+
+static int parse_argv(int argc, char **argv,
+ char ***the_modules,
+ char ***the_names) {
+
+ _cleanup_strv_free_ char **modules = NULL, **names = NULL;
+ const char *p;
+ int r;
+
+ p = getenv("SYSTEMD_TEST_NSS_BUFSIZE");
+ if (p) {
+ r = safe_atozu(p, &arg_bufsize);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse $SYSTEMD_TEST_NSS_BUFSIZE");
+ }
+
+ if (argc > 1)
+ modules = strv_new(argv[1]);
+ else
+ modules = strv_new(
+#if ENABLE_NSS_SYSTEMD
+ "systemd",
+#endif
+#if ENABLE_NSS_MYMACHINES
+ "mymachines",
+#endif
+ "files");
+ assert_se(modules);
+
+ if (argc > 2)
+ names = strv_copy(strv_skip(argv, 2));
+ else
+ names = strv_new("root",
+ NOBODY_USER_NAME,
+ "foo_no_such_user",
+ "0",
+ "65534");
+ assert_se(names);
+
+ *the_modules = TAKE_PTR(modules);
+ *the_names = TAKE_PTR(names);
+ return 0;
+}
+
+static int run(int argc, char **argv) {
+ _cleanup_free_ char *dir = NULL;
+ _cleanup_strv_free_ char **modules = NULL, **names = NULL;
+ char **module;
+ int r;
+
+ test_setup_logging(LOG_INFO);
+
+ r = parse_argv(argc, argv, &modules, &names);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse arguments: %m");
+
+ assert_se(path_extract_directory(argv[0], &dir) >= 0);
+
+ STRV_FOREACH(module, modules) {
+ r = test_one_module(dir, *module, names);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);