Skip to content

Commit

Permalink
Config file support
Browse files Browse the repository at this point in the history
The configuration file defines the default behaviour of pam_u2f.
Individual module invocations under /etc/pam.d can override
settings.

The file-system location of the config file is by default
$sysconfdir/security/pam_u2f.conf, where $sysconfdir is supplied at
build time.

A new module configuration, "conf_file=", allows to override it at
runtime. Only absolute paths are accepted.
  • Loading branch information
dacav committed Dec 17, 2024
1 parent 3fd267d commit 9038233
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 50 deletions.
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ AM_CPPFLAGS = $(LIBFIDO2_CFLAGS) $(LIBCRYPTO_CFLAGS)
if ENABLE_FUZZING
AM_CPPFLAGS += -fsanitize=fuzzer-no-link
endif
AM_CPPFLAGS += -D SYSCONFDIR='"$(sysconfdir)"'

noinst_LTLIBRARIES = libmodule.la
libmodule_la_SOURCES = pam-u2f.c
Expand Down
246 changes: 197 additions & 49 deletions cfg.c
Original file line number Diff line number Diff line change
@@ -1,65 +1,209 @@
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <security/pam_appl.h>
#include <security/pam_modules.h>

#include "cfg.h"
#include "debug.h"

int cfg_init(cfg_t *cfg, int flags, int argc, const char **argv) {
int i;
static int cfg_load_arg(cfg_t *cfg, const char *arg) {
if (strncmp(arg, "max_devices=", 12) == 0) {
sscanf(arg, "max_devices=%u", &cfg->max_devs);
} else if (strcmp(arg, "manual") == 0) {
cfg->manual = 1;
} else if (strcmp(arg, "debug") == 0) {
cfg->debug = 1;
} else if (strcmp(arg, "nouserok") == 0) {
cfg->nouserok = 1;
} else if (strcmp(arg, "openasuser") == 0) {
cfg->openasuser = 1;
} else if (strcmp(arg, "alwaysok") == 0) {
cfg->alwaysok = 1;
} else if (strcmp(arg, "interactive") == 0) {
cfg->interactive = 1;
} else if (strcmp(arg, "cue") == 0) {
cfg->cue = 1;
} else if (strcmp(arg, "nodetect") == 0) {
cfg->nodetect = 1;
} else if (strcmp(arg, "expand") == 0) {
cfg->expand = 1;
} else if (strncmp(arg, "userpresence=", 13) == 0) {
sscanf(arg, "userpresence=%d", &cfg->userpresence);
} else if (strncmp(arg, "userverification=", 17) == 0) {
sscanf(arg, "userverification=%d", &cfg->userverification);
} else if (strncmp(arg, "pinverification=", 16) == 0) {
sscanf(arg, "pinverification=%d", &cfg->pinverification);
} else if (strncmp(arg, "authfile=", 9) == 0) {
cfg->auth_file = arg + 9;
} else if (strcmp(arg, "sshformat") == 0) {
cfg->sshformat = 1;
} else if (strncmp(arg, "authpending_file=", 17) == 0) {
cfg->authpending_file = arg + 17;
} else if (strncmp(arg, "origin=", 7) == 0) {
cfg->origin = arg + 7;
} else if (strncmp(arg, "appid=", 6) == 0) {
cfg->appid = arg + 6;
} else if (strncmp(arg, "prompt=", 7) == 0) {
cfg->prompt = arg + 7;
} else if (strncmp(arg, "cue_prompt=", 11) == 0) {
cfg->cue_prompt = arg + 11;
} else if (strncmp(arg, "debug_file=", 11) == 0) {
const char *filename = arg + 11;
debug_close(cfg->debug_file);
cfg->debug_file = debug_open(filename);
} else
return PAM_AUTHINFO_UNAVAIL;

return PAM_SUCCESS;
}

static int slurp(int fd, size_t to_read, char **dst) {
char *buffer, *w;

buffer = malloc(to_read + 1);
if (!buffer)
return PAM_BUF_ERR;

w = buffer;
while (to_read) {
ssize_t r;

r = read(fd, w, to_read);
if (r < 0) {
free(buffer);
return PAM_SYSTEM_ERR;
}

if (r == 0)
break;

w += r;
to_read -= r;
}

*w = '\0';
*dst = buffer;
return PAM_SUCCESS;
}

static const char *ltrim(const char *s) {
while (isspace((unsigned char) *s))
s++;
return s;
}

static int cfg_load_defaults(cfg_t *cfg, const char *config_path) {
int fd, e;
struct stat st;
char *buffer = NULL, *saveptr = NULL;
const char *arg;

// If a config file other than the default is provided,
// the path must be absolute.
if (config_path && *config_path != '/')
return PAM_AUTHINFO_UNAVAIL;

fd = open(config_path ? config_path : CFG_DEFAULT_PATH,
O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW, 0);
if (fd == -1) {

// Only the default config file is allowed to be missing.
if (errno == ENOENT && !config_path)
return PAM_SUCCESS;

return PAM_AUTHINFO_UNAVAIL;
}

if (fstat(fd, &st)) {
e = PAM_SYSTEM_ERR;
goto exit;
}

if (!S_ISREG(st.st_mode)) {
e = PAM_AUTHINFO_UNAVAIL;
goto exit;
}

if (st.st_size == 0) {
e = PAM_SUCCESS;
goto exit;
}

if (st.st_size > CFG_MAX_FILE_SIZE) {
e = PAM_AUTHINFO_UNAVAIL;
goto exit;
}

e = slurp(fd, st.st_size, &buffer);
if (e)
goto exit;

arg = strtok_r(buffer, "\n", &saveptr);
while (arg) {
arg = ltrim(arg);

if (arg[0] != '\0' && arg[0] != '#') {
e = cfg_load_arg(cfg, arg);
if (e)
goto exit;
}

arg = strtok_r(NULL, "\n", &saveptr);
}

// Transfer buffer ownership
cfg->defaults_buffer = buffer;
buffer = NULL;
e = PAM_SUCCESS;

exit:
free(buffer);
close(fd);
return e;
}

static void cfg_reset(cfg_t *cfg) {
memset(cfg, 0, sizeof(cfg_t));
cfg->debug_file = DEFAULT_DEBUG_FILE;
cfg->userpresence = -1;
cfg->userverification = -1;
cfg->pinverification = -1;
}

for (i = 0; i < argc; i++) {
if (strncmp(argv[i], "max_devices=", 12) == 0) {
sscanf(argv[i], "max_devices=%u", &cfg->max_devs);
} else if (strcmp(argv[i], "manual") == 0) {
cfg->manual = 1;
} else if (strcmp(argv[i], "debug") == 0) {
cfg->debug = 1;
} else if (strcmp(argv[i], "nouserok") == 0) {
cfg->nouserok = 1;
} else if (strcmp(argv[i], "openasuser") == 0) {
cfg->openasuser = 1;
} else if (strcmp(argv[i], "alwaysok") == 0) {
cfg->alwaysok = 1;
} else if (strcmp(argv[i], "interactive") == 0) {
cfg->interactive = 1;
} else if (strcmp(argv[i], "cue") == 0) {
cfg->cue = 1;
} else if (strcmp(argv[i], "nodetect") == 0) {
cfg->nodetect = 1;
} else if (strcmp(argv[i], "expand") == 0) {
cfg->expand = 1;
} else if (strncmp(argv[i], "userpresence=", 13) == 0) {
sscanf(argv[i], "userpresence=%d", &cfg->userpresence);
} else if (strncmp(argv[i], "userverification=", 17) == 0) {
sscanf(argv[i], "userverification=%d", &cfg->userverification);
} else if (strncmp(argv[i], "pinverification=", 16) == 0) {
sscanf(argv[i], "pinverification=%d", &cfg->pinverification);
} else if (strncmp(argv[i], "authfile=", 9) == 0) {
cfg->auth_file = argv[i] + 9;
} else if (strcmp(argv[i], "sshformat") == 0) {
cfg->sshformat = 1;
} else if (strncmp(argv[i], "authpending_file=", 17) == 0) {
cfg->authpending_file = argv[i] + 17;
} else if (strncmp(argv[i], "origin=", 7) == 0) {
cfg->origin = argv[i] + 7;
} else if (strncmp(argv[i], "appid=", 6) == 0) {
cfg->appid = argv[i] + 6;
} else if (strncmp(argv[i], "prompt=", 7) == 0) {
cfg->prompt = argv[i] + 7;
} else if (strncmp(argv[i], "cue_prompt=", 11) == 0) {
cfg->cue_prompt = argv[i] + 11;
} else if (strncmp(argv[i], "debug_file=", 11) == 0) {
const char *filename = argv[i] + 11;
debug_close(cfg->debug_file);
cfg->debug_file = debug_open(filename);
int cfg_init(cfg_t *cfg, int flags, int argc, const char **argv) {
int i, e;
const char *config_path = NULL;

cfg_reset(cfg);

// Config file needs to be pre-loaded.
// If multiple conf_file= options appear, the last is honoured
// (consistently with any other option).
for (i = argc - 1; i >= 0; i--) {
if (strncmp(argv[i], "conf_file=", strlen("conf_file=")) == 0) {
config_path = argv[i] + strlen("conf_file=");
break;
}
}

e = cfg_load_defaults(cfg, config_path);
if (e != PAM_SUCCESS)
goto fail;

for (i = 0; i < argc; i++) {
if (strncmp(argv[i], "conf_file=", strlen("conf_file=")) == 0)
continue;
e = cfg_load_arg(cfg, argv[i]);
if (e != PAM_SUCCESS)
goto fail;
}

if (cfg->debug) {
debug_dbg(cfg, "called.");
debug_dbg(cfg, "flags %d argc %d", flags, argc);
Expand Down Expand Up @@ -87,11 +231,15 @@ int cfg_init(cfg_t *cfg, int flags, int argc, const char **argv) {
debug_dbg(cfg, "appid=%s", cfg->appid ? cfg->appid : "(null)");
debug_dbg(cfg, "prompt=%s", cfg->prompt ? cfg->prompt : "(null)");
}
return PAM_SUCCESS;

return 0;
fail:
cfg_free(cfg);
return e;
}

void cfg_free(cfg_t *cfg) {
debug_close(cfg->debug_file);
cfg->debug_file = DEFAULT_DEBUG_FILE;
free(cfg->defaults_buffer);
cfg_reset(cfg);
}
4 changes: 4 additions & 0 deletions cfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

#include <stdio.h>

#define CFG_DEFAULT_PATH (SYSCONFDIR "/security/pam_u2f.conf")
#define CFG_MAX_FILE_SIZE 1024 // Arbitrary

typedef struct {
unsigned max_devs;
int manual;
Expand All @@ -29,6 +32,7 @@ typedef struct {
const char *prompt;
const char *cue_prompt;
FILE *debug_file;
char *defaults_buffer;
} cfg_t;

int cfg_init(cfg_t *cfg, int flags, int argc, const char **argv);
Expand Down
4 changes: 3 additions & 1 deletion pam-u2f.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
int should_free_auth_file = 0;
int should_free_authpending_file = 0;

cfg_init(cfg, flags, argc, argv);
retval = cfg_init(cfg, flags, argc, argv);
if (retval != PAM_SUCCESS)
goto done;

PAM_MODUTIL_DEF_PRIVS(privs);

Expand Down

0 comments on commit 9038233

Please sign in to comment.