Skip to content

Commit

Permalink
Add unit tests for configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
dacav committed Dec 17, 2024
1 parent cb90fa2 commit 0dc31a0
Show file tree
Hide file tree
Showing 2 changed files with 269 additions and 0 deletions.
3 changes: 3 additions & 0 deletions tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ get_devices_LDADD = $(top_builddir)/libmodule.la
check_PROGRAMS += expand
expand_LDADD = $(top_builddir)/libmodule.la

check_PROGRAMS += cfg
cfg_LDADD = $(top_builddir)/libmodule.la

TESTS = $(check_PROGRAMS)
266 changes: 266 additions & 0 deletions tests/cfg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
#undef NDEBUG

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

#include <security/pam_appl.h>

#include "cfg.h"

static void write_conf(int fd, const char *text) {
size_t len;
int e;

len = strlen(text);
while (len) {
ssize_t w;

w = write(fd, text, len);
assert(w >= 0);

len -= w;
text += w;
}

e = fsync(fd);
assert(e == 0);
}

static char *generate_conf_file_arg(void) {
// Generate a conf_file= argument
//
// The function returns a string which is:
// - suitable as argv item for pam_u2f
// - suitable as template argument for mkstemp
// - referring to the absolute path of the temporary file.

char cwd[1024];
char *template;
int err;

err = !getcwd(cwd, sizeof(cwd));
assert(!err);

err = asprintf(&template, "conf_file=%.*s/test_config_XXXXXX",
(int) sizeof(cwd), cwd) == -1;
assert(!err);

return template;
}

static void test_regular(void) {
// Testing regular behaviour:
// - config file inheritance: the config file provides defaults,
// overrides from regular argv
// - comments
// - indentation

char *config_arg;
int fd, e;
cfg_t cfg;

config_arg = generate_conf_file_arg();

fd = mkstemp(config_arg + strlen("conf_file="));
assert(fd != -1);

write_conf(fd,
"max_devices=10\n"
"manual\n"
"debug\n"
"nouserok\n"
"openasuser\n"
"alwaysok\n"
"interactive\n"
"cue\n"
"nodetect\n"
"expand\n"
"userpresence=0\n"
"userverification=0\n"
"pinverification=0\n"
"authfile=/foo/bar\n"
"# sshformat\n" // Commented -> no effect
" # sshformat\n" // also commented
" authpending_file=/baz/quux\n" // Indented -> accepted
"origin=pam://lolcalhost\n"
"appid=pam://lolcalhost\n"
"prompt=hello\n"
"cue_prompt=howdy\n"
"debug_file=stdout\n");

const char *argv[] = {
config_arg, "prompt=hi",
"debug", // So we have a log file for the test
};

e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e == PAM_SUCCESS);

unlink(config_arg + strlen("conf_file="));
free(config_arg);
close(fd);

assert(cfg.alwaysok);
assert(strcmp(cfg.auth_file, "/foo/bar") == 0);
assert(!cfg.sshformat); // setting is commented out
assert(strcmp(cfg.authpending_file, "/baz/quux") == 0);
assert(strcmp(cfg.origin, "pam://lolcalhost") == 0);
assert(strcmp(cfg.appid, "pam://lolcalhost") == 0);
assert(strcmp(cfg.prompt, "hi") == 0);
assert(strcmp(cfg.cue_prompt, "howdy") == 0);
assert(cfg.debug_file == stdout);

cfg_free(&cfg);
}

static void test_config_abspath(void) {
/* Ensuring that the library rejects the conf_file= argument
* unless it points to an absolute path.
*/

const char *argv[] = {
NULL, // replaced with config_arg_{...}
"debug", // So we have a log file for the test
};

char config_arg_relative[] = "conf_file=test_config_XXXXXX";
char *config_arg_absolute;
int fd, e;
cfg_t cfg;

// 1. Generate a valid configuration and pass it around
// as relative path. Assert failure.
fd = mkstemp(config_arg_relative + strlen("conf_file="));
assert(fd != -1);
write_conf(fd, "alwaysok\n"
"prompt=hello");

argv[0] = config_arg_relative;
e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e != PAM_SUCCESS);

unlink(config_arg_relative + strlen("conf_file="));
close(fd);

// 2. Generate a same configuration and pass it around
// as absolute path. Assert success.

config_arg_absolute = generate_conf_file_arg();

fd = mkstemp(config_arg_absolute + strlen("conf_file="));
assert(fd != -1);
write_conf(fd, "alwaysok\n"
"prompt=hello");

argv[0] = config_arg_absolute;
e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e == PAM_SUCCESS);

assert(strcmp(cfg.prompt, "hello") == 0);
cfg_free(&cfg);

unlink(config_arg_absolute + strlen("conf_file="));
close(fd);
free(config_arg_absolute);
}

static void test_last_config_wins(void) {
// If conf_file= is used multiple times, only
// the last one is honored.

const char *argv[3] = {NULL, NULL, "debug"};
char *config_arg1, *config_arg2;
int fd1, fd2, e;
cfg_t cfg;

config_arg1 = generate_conf_file_arg();
fd1 = mkstemp(config_arg1 + strlen("conf_file="));
assert(fd1 != -1);

config_arg2 = generate_conf_file_arg();
fd2 = mkstemp(config_arg2 + strlen("conf_file="));
assert(fd2 != -1);

write_conf(fd1, "max_devices=10\n");
write_conf(fd2, "max_devices=12\n");

argv[0] = config_arg1;
argv[1] = config_arg2;

e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e == PAM_SUCCESS);
assert(cfg.max_devs == 12);
cfg_free(&cfg);

argv[0] = config_arg2;
argv[1] = config_arg1;
e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e == PAM_SUCCESS);
assert(cfg.max_devs == 10);
cfg_free(&cfg);

unlink(config_arg1 + strlen("conf_file="));
free(config_arg1);
close(fd1);

unlink(config_arg2 + strlen("conf_file="));
free(config_arg2);
close(fd2);
}

static void test_corner_cases(void) {
// Testng config file corner cases.

const char *argv[] = {NULL, "debug"};
char *config_arg;
int fd, e;
size_t size = 0;
cfg_t cfg;
ssize_t w;

config_arg = generate_conf_file_arg();
argv[0] = config_arg;

fd = mkstemp(config_arg + strlen("conf_file="));
assert(fd != -1);

// 1. Empty file -> Success
e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e == PAM_SUCCESS);
cfg_free(&cfg);

// 2. File size within limit (1KiB) -> Success
while (size + strlen("manual\n") < CFG_MAX_FILE_SIZE) {
w = write(fd, "manual\n", sizeof("manual\n"));
assert(w >= 0);
size += w;
}
e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e == PAM_SUCCESS);
cfg_free(&cfg);

// 3. File size beyond limit -> Failure
w = write(fd, "manual\n", strlen("manual\n"));
assert(w == strlen("manual\n"));
e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e != PAM_SUCCESS);

// 4. Missing file -> Failure
unlink(config_arg + strlen("conf_file="));
e = cfg_init(&cfg, 0, sizeof(argv) / sizeof(*argv), argv);
assert(e != PAM_SUCCESS);

free(config_arg);
close(fd);
}

int main(int argc, char **argv) {
(void) argc, (void) argv;

test_regular();
test_config_abspath();
test_last_config_wins();
test_corner_cases();
}

0 comments on commit 0dc31a0

Please sign in to comment.