From f2feb94748bd3c64ed153461afa51aebbd717821 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Wed, 2 Dec 2015 11:09:51 -0500 Subject: test: Add helper library to fake passwd/group files The requirements for testing are currently that the system have several users and groups predefined. The chosen users and groups are typically found on every system so that's a safe bet. However, tests for the quote code may involve more esoteric names that include backslashes or other quoted characters. This patch adds a helper library that implements the passwd and group calls and redirects the reads to project-defined passwd and group files. That way, we know for certain that those usernames and groups will be found during testing. The helper library is enabled using LD_PRELOAD via a wrapper script. The library will not be installed as part of the project's make install. Since the local user might not be found in the local test.{group,passwd}, we extend test/run to use numeric uid/gids if the lookup fails. --- test/Makemodule.am | 11 +++- test/run | 17 ++++-- test/runwrapper | 6 +++ test/test.group | 3 ++ test/test.passwd | 5 ++ test/test_group.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_passwd.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 341 insertions(+), 4 deletions(-) create mode 100755 test/runwrapper create mode 100644 test/test.group create mode 100644 test/test.passwd create mode 100644 test/test_group.c create mode 100644 test/test_passwd.c diff --git a/test/Makemodule.am b/test/Makemodule.am index 7b8dafe..488d17e 100644 --- a/test/Makemodule.am +++ b/test/Makemodule.am @@ -20,8 +20,17 @@ EXTRA_DIST += \ test/make-tree \ test/malformed-restore-double-owner.acl \ test/run \ + test/runwrapper \ test/sort-getfacl-output \ + test/test.passwd \ + test/test.group \ $(TESTS) +check_LTLIBRARIES = libtestlookup.la + +libtestlookup_la_SOURCES = test/test_passwd.c test/test_group.c +libtestlookup_la_CFLAGS = -DBASEDIR=\"$(abs_srcdir)\" +libtestlookup_la_LDFLAGS = -rpath $(abs_builddir) + AM_TESTS_ENVIRONMENT = PATH="$(abs_top_builddir):$$PATH"; -TEST_LOG_COMPILER = $(srcdir)/test/run +TEST_LOG_COMPILER = $(srcdir)/test/runwrapper diff --git a/test/run b/test/run index fcbcf29..721b58a 100755 --- a/test/run +++ b/test/run @@ -43,12 +43,12 @@ use File::Basename qw(basename dirname); use File::Path qw(rmtree); use Getopt::Std; use POSIX qw(isatty setuid getcwd); -use vars qw($opt_l $opt_v); +use vars qw($opt_l $opt_v $opt_t); no warnings qw(taint); $opt_l = ~0; # a really huge number -getopts('l:v'); +getopts('l:vt:'); my ($OK, $FAILED) = ("ok", "failed"); if (isatty(fileno(STDOUT))) { @@ -63,7 +63,18 @@ $ENV{"PATH"} = $ENV{"TESTDIR"} . ":$ENV{PATH}"; # Add the parent dir to PATH so we can find the compiled tools. $ENV{"PATH"} = dirname(abs_path(dirname($0))) . ":$ENV{PATH}"; $ENV{"TUSER"} = getpwuid($>); +if (!defined($ENV{"TUSER"})) { + # If the uid isn't found in the private passwd file, just use the + # uid directly. + $ENV{"TUSER"} = $>; +} $ENV{"TGROUP"} = getgrgid($)); +if (!defined($ENV{"TGROUP"})) { + # If the groupid isn't found in the private group file, just use the + # gid directly. + my @groups = split(/ /, $(); + $ENV{"TGROUP"} = $groups[0]; +} open(TEST_FILE, $ARGV[0]); @@ -92,7 +103,7 @@ for (;;) { if (defined $line) { # Substitute %VAR and %{VAR} with environment variables. $line =~ s[%(\w+)][$ENV{$1}]eg; - $line =~ s[%{(\w+)}][$ENV{$1}]eg; + $line =~ s[%\{(\w+)\}][$ENV{$1}]eg; } if (defined $line) { if ($line =~ s/^\s*< ?//) { diff --git a/test/runwrapper b/test/runwrapper new file mode 100755 index 0000000..38de337 --- /dev/null +++ b/test/runwrapper @@ -0,0 +1,6 @@ +#!/bin/bash +if [ -e "$PWD/.libs/libtestlookup.so" ]; then + export LD_PRELOAD="$PWD/.libs/libtestlookup.so" +fi + +$PWD/test/run $@ diff --git a/test/test.group b/test/test.group new file mode 100644 index 0000000..5f9ca8f --- /dev/null +++ b/test/test.group @@ -0,0 +1,3 @@ +bin:x:1:daemon +daemon:x:2: +users:x:100: diff --git a/test/test.passwd b/test/test.passwd new file mode 100644 index 0000000..a917bdd --- /dev/null +++ b/test/test.passwd @@ -0,0 +1,5 @@ +root:x:0:0:root:/:/bin/false +bin:x:1:1:bin:/:/bin/false +daemon:x:2:2:Daemon:/:/bin/false +domain\user:x:3:3:Test user:/:/bin/false +domain\12345:x:4:4:Test user:/:/bin/false diff --git a/test/test_group.c b/test/test_group.c new file mode 100644 index 0000000..7fe6a19 --- /dev/null +++ b/test/test_group.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_GROUP "test/test.group" +static char grfile[PATH_MAX]; +static void setup_grfile() __attribute__((constructor)); + +void setup_grfile() { + snprintf(grfile, sizeof(grfile), "%s/%s", BASEDIR, TEST_GROUP); +} + +#define ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask)) +#define ALIGN(x, a) ALIGN_MASK(x, (typeof(x))(a) - 1) + +int test_getgrent_r(FILE *file, struct group *grp, char *buf, + size_t buflen, struct group **result) +{ + char *line, *str, *remain; + int count, index = 0; + int gr_mem_cnt = 0; + + *result = NULL; + + line = fgets(buf, buflen, file); + if (!line) + return 0; + + /* We'll stuff the gr_mem array in the remaining space in the buffer */ + remain = buf + ALIGN(line + strlen(line) - buf, sizeof(char *)); + grp->gr_mem = (char **)remain; + count = (buf + buflen - remain) / sizeof (char *); + if (!count) { + errno = ERANGE; + return -1; + } + + grp->gr_mem[--count] = NULL; + + while ((str = strtok(line, ":"))) { + char *ptr; + switch (index++) { + case 0: + grp->gr_name = str; + break; + case 1: + grp->gr_passwd = str; + break; + case 2: + errno = 0; + grp->gr_gid = strtol(str, NULL, 10); + if (errno) + return -1; + break; + case 3: + while ((str = strtok_r(str, ",", &ptr))) { + if (count-- <= 0) { + errno = ERANGE; + return -1; + } + grp->gr_mem[gr_mem_cnt++] = str; + str = NULL; + } + } + line = NULL; + } + + *result = grp; + + return 0; +} + +int test_getgr_match(struct group *grp, char *buf, size_t buflen, + struct group **result, + int (*match)(const struct group *, const void *), + const void *data) +{ + FILE *file; + struct group *_result; + + *result = NULL; + + file = fopen(grfile, "r"); + if (!file) { + errno = EBADF; + return -1; + } + + errno = 0; + while (!test_getgrent_r(file, grp, buf, buflen, &_result)) { + if (!_result) + break; + else if (match(grp, data)) { + *result = grp; + break; + } + } + + fclose(file); + if (!errno && !*result) + errno = ENOENT; + if (errno) + return -1; + return 0; +} + +static int match_name(const struct group *grp, const void *data) +{ + const char *name = data; + return !strcmp(grp->gr_name, name); +} + +int getgrnam_r(const char *name, struct group *grp, char *buf, size_t buflen, + struct group **result) +{ + return test_getgr_match(grp, buf, buflen, result, match_name, name); +} + +struct group *getgrnam(const char *name) +{ + static char buf[16384]; + static struct group grp; + struct group *result; + + (void) getgrnam_r(name, &grp, buf, sizeof(buf), &result); + return result; +} + +static int match_gid(const struct group *grp, const void *data) +{ + gid_t gid = *(gid_t *)data; + return grp->gr_gid == gid; +} + +int getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen, + struct group **result) +{ + return test_getgr_match(grp, buf, buflen, result, match_gid, &gid); +} + +struct group *getgrgid(gid_t gid) +{ + static char buf[16384]; + static struct group grp; + struct group *result; + + (void) getgrgid_r(gid, &grp, buf, sizeof(buf), &result); + return result; +} diff --git a/test/test_passwd.c b/test/test_passwd.c new file mode 100644 index 0000000..b5052f8 --- /dev/null +++ b/test/test_passwd.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_PASSWD "test/test.passwd" +static char pwfile[PATH_MAX]; +static void setup_pwfile() __attribute__((constructor)); + +void setup_pwfile() { + snprintf(pwfile, sizeof(pwfile), "%s/%s", BASEDIR, TEST_PASSWD); +} + +#define ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask)) +#define ALIGN(x, a) ALIGN_MASK(x, (typeof(x))(a) - 1) + +int test_getpwent_r(FILE *file, struct passwd *pwd, char *buf, + size_t buflen, struct passwd **result) +{ + char *str, *line; + int index = 0; + + *result = NULL; + + line = fgets(buf, buflen, file); + if (!line) { + return 0; + } + + while ((str = strtok(line, ":"))) { + switch (index++) { + case 0: + pwd->pw_name = str; + break; + case 1: + pwd->pw_passwd = str; + break; + case 2: + errno = 0; + pwd->pw_uid = strtol(str, NULL, 10); + if (errno) + return -1; + break; + case 3: + errno = 0; + pwd->pw_gid = strtol(str, NULL, 10); + if (errno) + return -1; + break; + case 4: + pwd->pw_gecos = str; + break; + case 5: + pwd->pw_dir = str; + break; + case 6: + pwd->pw_shell = str; + break; + } + line = NULL; + } + + *result = pwd; + + return 0; +} + +int test_getpw_match(struct passwd *pwd, char *buf, size_t buflen, + struct passwd **result, + int (*match)(const struct passwd *, const void *), + const void *data) +{ + FILE *file; + struct passwd *_result; + + *result = NULL; + + file = fopen(pwfile, "r"); + if (!file) { + fprintf(stderr, "Failed to open %s\n", pwfile); + errno = EBADF; + return -1; + } + + errno = 0; + while (!test_getpwent_r(file, pwd, buf, buflen, &_result)) { + if (!_result) + break; + else if (match(pwd, data)) { + *result = pwd; + break; + } + } + + fclose(file); + if (!errno && !*result) + errno = ENOENT; + if (errno) + return -1; + return 0; +} + +static int match_name(const struct passwd *pwd, const void *data) +{ + const char *name = data; + return !strcmp(pwd->pw_name, name); +} + +int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, + struct passwd **result) +{ + return test_getpw_match(pwd, buf, buflen, result, match_name, name); +} + +struct passwd *getpwnam(const char *name) +{ + static char buf[16384]; + static struct passwd pwd; + struct passwd *result; + + (void) getpwnam_r(name, &pwd, buf, sizeof(buf), &result); + return result; +} + +static int match_uid(const struct passwd *pwd, const void *data) +{ + uid_t uid = *(uid_t *)data; + return pwd->pw_uid == uid; +} + +int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, + struct passwd **result) +{ + return test_getpw_match(pwd, buf, buflen, result, match_uid, &uid); +} + +struct passwd *getpwuid(uid_t uid) +{ + static char buf[16384]; + static struct passwd pwd; + struct passwd *result; + + (void) getpwuid_r(uid, &pwd, buf, sizeof(buf), &result); + return result; +} -- cgit v1.2.1