diff --git a/Makefile.am b/Makefile.am index e67d64c..f3272e1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/cfg.c b/cfg.c index 68ad532..3e680ea 100644 --- a/cfg.c +++ b/cfg.c @@ -1,65 +1,209 @@ +#include +#include +#include +#include #include +#include +#include + +#include +#include #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); @@ -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); } diff --git a/cfg.h b/cfg.h index 32ac16c..9f36182 100644 --- a/cfg.h +++ b/cfg.h @@ -7,6 +7,9 @@ #include +#define CFG_DEFAULT_PATH (SYSCONFDIR "/security/pam_u2f.conf") +#define CFG_MAX_FILE_SIZE 1024 // Arbitrary + typedef struct { unsigned max_devs; int manual; @@ -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); diff --git a/pam-u2f.c b/pam-u2f.c index 2d21ed3..f897b2a 100644 --- a/pam-u2f.c +++ b/pam-u2f.c @@ -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);