diff -up Linux-PAM-1.3.1/modules/pam_faillock/faillock.8.xml.faillock-load-conf-from-file Linux-PAM-1.3.1/modules/pam_faillock/faillock.8.xml --- Linux-PAM-1.3.1/modules/pam_faillock/faillock.8.xml.faillock-load-conf-from-file 2022-05-26 10:57:11.713067506 +0200 +++ Linux-PAM-1.3.1/modules/pam_faillock/faillock.8.xml 2022-05-26 10:57:11.747067749 +0200 @@ -57,12 +57,29 @@ + + + + + The file where the configuration is located. The default is + /etc/security/faillock.conf. + + + + + - The directory where the user files with the failure records are kept. The - default is /var/run/faillock. + The directory where the user files with the failure records are kept. + + + The priority to set this option is to use the value provided + from the command line. If this isn't provided, then the value + from the configuration file is used. Finally, if neither of + them has been provided, then + /var/run/faillock is used. diff -up Linux-PAM-1.3.1/modules/pam_faillock/faillock_config.c.faillock-load-conf-from-file Linux-PAM-1.3.1/modules/pam_faillock/faillock_config.c --- Linux-PAM-1.3.1/modules/pam_faillock/faillock_config.c.faillock-load-conf-from-file 2022-05-26 10:57:11.747067749 +0200 +++ Linux-PAM-1.3.1/modules/pam_faillock/faillock_config.c 2022-05-26 10:57:11.747067749 +0200 @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2022 Tomas Mraz + * Copyright (c) 2022 Iker Pedrosa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "faillock_config.h" +#include "faillock.h" + +#define FAILLOCK_DEFAULT_CONF "/etc/security/faillock.conf" +#ifdef VENDOR_SCONFIGDIR +#define VENDOR_FAILLOCK_DEFAULT_CONF VENDOR_SCONFIGDIR "/faillock.conf" +#endif + +static void PAM_FORMAT((printf, 3, 4)) PAM_NONNULL((3)) +config_log(const pam_handle_t *pamh, int priority, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + if (pamh) { + pam_vsyslog(pamh, priority, fmt, args); + } else { + char *buf = NULL; + + if (vasprintf(&buf, fmt, args) < 0) { + fprintf(stderr, "vasprintf: %m"); + va_end(args); + return; + } + fprintf(stderr, "%s\n", buf); + free(buf); + } + va_end(args); +} + +/* parse a single configuration file */ +int +read_config_file(pam_handle_t *pamh, struct options *opts, const char *cfgfile) +{ + char linebuf[FAILLOCK_CONF_MAX_LINELEN+1]; + const char *fname = (cfgfile != NULL) ? cfgfile : FAILLOCK_DEFAULT_CONF; + FILE *f = fopen(fname, "r"); + +#ifdef VENDOR_FAILLOCK_DEFAULT_CONF + if (f == NULL && errno == ENOENT && cfgfile == NULL) { + /* + * If the default configuration file in /etc does not exist, + * try the vendor configuration file as fallback. + */ + f = fopen(VENDOR_FAILLOCK_DEFAULT_CONF, "r"); + } +#endif /* VENDOR_FAILLOCK_DEFAULT_CONF */ + + if (f == NULL) { + /* ignore non-existent default config file */ + if (errno == ENOENT && cfgfile == NULL) + return PAM_SUCCESS; + return PAM_SERVICE_ERR; + } + + while (fgets(linebuf, sizeof(linebuf), f) != NULL) { + size_t len; + char *ptr; + char *name; + int eq; + + len = strlen(linebuf); + /* len cannot be 0 unless there is a bug in fgets */ + if (len && linebuf[len - 1] != '\n' && !feof(f)) { + (void) fclose(f); + return PAM_SERVICE_ERR; + } + + if ((ptr=strchr(linebuf, '#')) != NULL) { + *ptr = '\0'; + } else { + ptr = linebuf + len; + } + + /* drop terminating whitespace including the \n */ + while (ptr > linebuf) { + if (!isspace(*(ptr-1))) { + *ptr = '\0'; + break; + } + --ptr; + } + + /* skip initial whitespace */ + for (ptr = linebuf; isspace(*ptr); ptr++); + if (*ptr == '\0') + continue; + + /* grab the key name */ + eq = 0; + name = ptr; + while (*ptr != '\0') { + if (isspace(*ptr) || *ptr == '=') { + eq = *ptr == '='; + *ptr = '\0'; + ++ptr; + break; + } + ++ptr; + } + + /* grab the key value */ + while (*ptr != '\0') { + if (*ptr != '=' || eq) { + if (!isspace(*ptr)) { + break; + } + } else { + eq = 1; + } + ++ptr; + } + + /* set the key:value pair on opts */ + set_conf_opt(pamh, opts, name, ptr); + } + + (void)fclose(f); + return PAM_SUCCESS; +} + +void +set_conf_opt(pam_handle_t *pamh, struct options *opts, const char *name, + const char *value) +{ + if (strcmp(name, "dir") == 0) { + if (value[0] != '/') { + config_log(pamh, LOG_ERR, + "Tally directory is not absolute path (%s); keeping value", + value); + } else { + free(opts->dir); + opts->dir = strdup(value); + if (opts->dir == NULL) { + opts->fatal_error = 1; + config_log(pamh, LOG_CRIT, "Error allocating memory: %m"); + } + } + } + else if (strcmp(name, "deny") == 0) { + if (sscanf(value, "%hu", &opts->deny) != 1) { + config_log(pamh, LOG_ERR, + "Bad number supplied for deny argument"); + } + } + else if (strcmp(name, "fail_interval") == 0) { + unsigned int temp; + if (sscanf(value, "%u", &temp) != 1 || + temp > MAX_TIME_INTERVAL) { + config_log(pamh, LOG_ERR, + "Bad number supplied for fail_interval argument"); + } else { + opts->fail_interval = temp; + } + } + else if (strcmp(name, "unlock_time") == 0) { + unsigned int temp; + + if (strcmp(value, "never") == 0) { + opts->unlock_time = 0; + } + else if (sscanf(value, "%u", &temp) != 1 || + temp > MAX_TIME_INTERVAL) { + config_log(pamh, LOG_ERR, + "Bad number supplied for unlock_time argument"); + } + else { + opts->unlock_time = temp; + } + } + else if (strcmp(name, "root_unlock_time") == 0) { + unsigned int temp; + + if (strcmp(value, "never") == 0) { + opts->root_unlock_time = 0; + } + else if (sscanf(value, "%u", &temp) != 1 || + temp > MAX_TIME_INTERVAL) { + config_log(pamh, LOG_ERR, + "Bad number supplied for root_unlock_time argument"); + } else { + opts->root_unlock_time = temp; + } + } + else if (strcmp(name, "admin_group") == 0) { + free(opts->admin_group); + opts->admin_group = strdup(value); + if (opts->admin_group == NULL) { + opts->fatal_error = 1; + config_log(pamh, LOG_CRIT, "Error allocating memory: %m"); + } + } + else if (strcmp(name, "even_deny_root") == 0) { + opts->flags |= FAILLOCK_FLAG_DENY_ROOT; + } + else if (strcmp(name, "audit") == 0) { + opts->flags |= FAILLOCK_FLAG_AUDIT; + } + else if (strcmp(name, "silent") == 0) { + opts->flags |= FAILLOCK_FLAG_SILENT; + } + else if (strcmp(name, "no_log_info") == 0) { + opts->flags |= FAILLOCK_FLAG_NO_LOG_INFO; + } + else if (strcmp(name, "local_users_only") == 0) { + opts->flags |= FAILLOCK_FLAG_LOCAL_ONLY; + } + else if (strcmp(name, "nodelay") == 0) { + opts->flags |= FAILLOCK_FLAG_NO_DELAY; + } + else { + config_log(pamh, LOG_ERR, "Unknown option: %s", name); + } +} + +const char *get_tally_dir(const struct options *opts) +{ + return (opts->dir != NULL) ? opts->dir : FAILLOCK_DEFAULT_TALLYDIR; +} diff -up Linux-PAM-1.3.1/modules/pam_faillock/faillock_config.h.faillock-load-conf-from-file Linux-PAM-1.3.1/modules/pam_faillock/faillock_config.h --- Linux-PAM-1.3.1/modules/pam_faillock/faillock_config.h.faillock-load-conf-from-file 2022-05-26 10:57:11.747067749 +0200 +++ Linux-PAM-1.3.1/modules/pam_faillock/faillock_config.h 2022-05-26 10:57:11.747067749 +0200 @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2022 Tomas Mraz + * Copyright (c) 2022 Iker Pedrosa + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * faillock_config.h - load configuration options from file + * + */ + +#ifndef _FAILLOCK_CONFIG_H +#define _FAILLOCK_CONFIG_H + +#include +#include +#include + +#include + +#define FAILLOCK_FLAG_DENY_ROOT 0x1 +#define FAILLOCK_FLAG_AUDIT 0x2 +#define FAILLOCK_FLAG_SILENT 0x4 +#define FAILLOCK_FLAG_NO_LOG_INFO 0x8 +#define FAILLOCK_FLAG_UNLOCKED 0x10 +#define FAILLOCK_FLAG_LOCAL_ONLY 0x20 +#define FAILLOCK_FLAG_NO_DELAY 0x40 + +#define FAILLOCK_CONF_MAX_LINELEN 1023 +#define MAX_TIME_INTERVAL 604800 /* 7 days */ + +struct options { + unsigned int action; + unsigned int flags; + unsigned short deny; + unsigned int fail_interval; + unsigned int unlock_time; + unsigned int root_unlock_time; + char *dir; + const char *user; + char *admin_group; + int failures; + uint64_t latest_time; + uid_t uid; + int is_admin; + uint64_t now; + int fatal_error; + + unsigned int reset; + const char *progname; +}; + +int read_config_file(pam_handle_t *pamh, struct options *opts, + const char *cfgfile); +void set_conf_opt(pam_handle_t *pamh, struct options *opts, const char *name, + const char *value); +const char *get_tally_dir(const struct options *opts); + +#endif /* _FAILLOCK_CONFIG_H */ diff -up Linux-PAM-1.3.1/modules/pam_faillock/main.c.faillock-load-conf-from-file Linux-PAM-1.3.1/modules/pam_faillock/main.c --- Linux-PAM-1.3.1/modules/pam_faillock/main.c.faillock-load-conf-from-file 2022-05-26 10:57:11.713067506 +0200 +++ Linux-PAM-1.3.1/modules/pam_faillock/main.c 2022-05-26 10:57:11.747067749 +0200 @@ -48,43 +48,50 @@ #include #endif +#include "pam_inline.h" #include "faillock.h" - -struct options { - unsigned int reset; - const char *dir; - const char *user; - const char *progname; -}; +#include "faillock_config.h" static int args_parse(int argc, char **argv, struct options *opts) { int i; + int rv; + const char *dir = NULL; + const char *conf = NULL; + memset(opts, 0, sizeof(*opts)); - opts->dir = FAILLOCK_DEFAULT_TALLYDIR; opts->progname = argv[0]; for (i = 1; i < argc; ++i) { - - if (strcmp(argv[i], "--dir") == 0) { + if (strcmp(argv[i], "--conf") == 0) { + ++i; + if (i >= argc || strlen(argv[i]) == 0) { + fprintf(stderr, "%s: No configuration file supplied.\n", + argv[0]); + return -1; + } + conf = argv[i]; + } + else if (strcmp(argv[i], "--dir") == 0) { ++i; if (i >= argc || strlen(argv[i]) == 0) { - fprintf(stderr, "%s: No directory supplied.\n", argv[0]); + fprintf(stderr, "%s: No records directory supplied.\n", + argv[0]); return -1; } - opts->dir = argv[i]; - } + dir = argv[i]; + } else if (strcmp(argv[i], "--user") == 0) { ++i; if (i >= argc || strlen(argv[i]) == 0) { - fprintf(stderr, "%s: No user name supplied.\n", argv[0]); + fprintf(stderr, "%s: No user name supplied.\n", argv[0]); return -1; } - opts->user = argv[i]; + opts->user = argv[i]; } - else if (strcmp(argv[i], "--reset") == 0) { + else if (strcmp(argv[i], "--reset") == 0) { opts->reset = 1; } else { @@ -92,6 +99,21 @@ args_parse(int argc, char **argv, struct return -1; } } + + if ((rv = read_config_file(NULL, opts, conf)) != PAM_SUCCESS) { + fprintf(stderr, "Configuration file missing or broken"); + return rv; + } + + if (dir != NULL) { + free(opts->dir); + opts->dir = strdup(dir); + if (opts->dir == NULL) { + fprintf(stderr, "Error allocating memory: %m"); + return -1; + } + } + return 0; } @@ -109,10 +131,11 @@ do_user(struct options *opts, const char int rv; struct tally_data tallies; struct passwd *pwd; + const char *dir = get_tally_dir(opts); pwd = getpwnam(user); - fd = open_tally(opts->dir, user, pwd != NULL ? pwd->pw_uid : 0, 0); + fd = open_tally(dir, user, pwd != NULL ? pwd->pw_uid : 0, 0); if (fd == -1) { if (errno == ENOENT) { @@ -189,11 +212,11 @@ do_allusers(struct options *opts) { struct dirent **userlist; int rv, i; + const char *dir = get_tally_dir(opts); - rv = scandir(opts->dir, &userlist, NULL, alphasort); + rv = scandir(dir, &userlist, NULL, alphasort); if (rv < 0) { - fprintf(stderr, "%s: Error reading tally directory: ", opts->progname); - perror(NULL); + fprintf(stderr, "%s: Error reading tally directory: %m\n", opts->progname); return 2; } diff -up Linux-PAM-1.3.1/modules/pam_faillock/Makefile.am.faillock-load-conf-from-file Linux-PAM-1.3.1/modules/pam_faillock/Makefile.am --- Linux-PAM-1.3.1/modules/pam_faillock/Makefile.am.faillock-load-conf-from-file 2022-05-26 10:57:11.727067606 +0200 +++ Linux-PAM-1.3.1/modules/pam_faillock/Makefile.am 2022-05-26 10:57:59.032406450 +0200 @@ -17,7 +17,7 @@ TESTS = tst-pam_faillock securelibdir = $(SECUREDIR) secureconfdir = $(SCONFIGDIR) -noinst_HEADERS = faillock.h +noinst_HEADERS = faillock.h faillock_config.h faillock_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include @PIE_CFLAGS@ pam_faillock_la_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include @@ -36,8 +36,8 @@ secureconf_DATA = faillock.conf securelib_LTLIBRARIES = pam_faillock.la sbin_PROGRAMS = faillock -pam_faillock_la_SOURCES = pam_faillock.c faillock.c -faillock_SOURCES = main.c faillock.c +pam_faillock_la_SOURCES = pam_faillock.c faillock.c faillock_config.c +faillock_SOURCES = main.c faillock.c faillock_config.c if ENABLE_REGENERATE_MAN noinst_DATA = README diff -up Linux-PAM-1.3.1/modules/pam_faillock/pam_faillock.c.faillock-load-conf-from-file Linux-PAM-1.3.1/modules/pam_faillock/pam_faillock.c --- Linux-PAM-1.3.1/modules/pam_faillock/pam_faillock.c.faillock-load-conf-from-file 2022-05-26 10:57:11.727067606 +0200 +++ Linux-PAM-1.3.1/modules/pam_faillock/pam_faillock.c 2022-05-26 10:57:11.748067756 +0200 @@ -38,7 +38,6 @@ #include #include #include -#include #include #include #include @@ -54,83 +53,50 @@ #include #include +#include "pam_inline.h" #include "faillock.h" - -#define PAM_SM_AUTH -#define PAM_SM_ACCOUNT +#include "faillock_config.h" #define FAILLOCK_ACTION_PREAUTH 0 #define FAILLOCK_ACTION_AUTHSUCC 1 #define FAILLOCK_ACTION_AUTHFAIL 2 -#define FAILLOCK_FLAG_DENY_ROOT 0x1 -#define FAILLOCK_FLAG_AUDIT 0x2 -#define FAILLOCK_FLAG_SILENT 0x4 -#define FAILLOCK_FLAG_NO_LOG_INFO 0x8 -#define FAILLOCK_FLAG_UNLOCKED 0x10 -#define FAILLOCK_FLAG_LOCAL_ONLY 0x20 - -#define MAX_TIME_INTERVAL 604800 /* 7 days */ -#define FAILLOCK_CONF_MAX_LINELEN 1023 -#define FAILLOCK_ERROR_CONF_OPEN -3 -#define FAILLOCK_ERROR_CONF_MALFORMED -4 - -#define PATH_PASSWD "/etc/passwd" - -struct options { - unsigned int action; - unsigned int flags; - unsigned short deny; - unsigned int fail_interval; - unsigned int unlock_time; - unsigned int root_unlock_time; - char *dir; - const char *conf; - const char *user; - char *admin_group; - int failures; - uint64_t latest_time; - uid_t uid; - int is_admin; - uint64_t now; - int fatal_error; -}; - -int read_config_file( - pam_handle_t *pamh, - struct options *opts, - const char *cfgfile -); - -void set_conf_opt( - pam_handle_t *pamh, - struct options *opts, - const char *name, - const char *value -); - -static void +static int args_parse(pam_handle_t *pamh, int argc, const char **argv, int flags, struct options *opts) { int i; + int config_arg_index = -1; int rv; + const char *conf = NULL; + memset(opts, 0, sizeof(*opts)); - opts->dir = strdup(FAILLOCK_DEFAULT_TALLYDIR); - opts->conf = FAILLOCK_DEFAULT_CONF; opts->deny = 3; opts->fail_interval = 900; opts->unlock_time = 600; opts->root_unlock_time = MAX_TIME_INTERVAL+1; - if ((rv=read_config_file(pamh, opts, opts->conf)) != PAM_SUCCESS) { - pam_syslog(pamh, LOG_DEBUG, - "Configuration file missing"); + for (i = 0; i < argc; ++i) { + const char *str = pam_str_skip_prefix(argv[i], "conf="); + + if (str != NULL) { + conf = str; + config_arg_index = i; + } + } + + if ((rv = read_config_file(pamh, opts, conf)) != PAM_SUCCESS) { + pam_syslog(pamh, LOG_ERR, + "Configuration file missing or broken"); + return rv; } for (i = 0; i < argc; ++i) { - if (strcmp(argv[i], "preauth") == 0) { + if (i == config_arg_index) { + continue; + } + else if (strcmp(argv[i], "preauth") == 0) { opts->action = FAILLOCK_ACTION_PREAUTH; } else if (strcmp(argv[i], "authfail") == 0) { @@ -163,226 +129,26 @@ args_parse(pam_handle_t *pamh, int argc, if (flags & PAM_SILENT) opts->flags |= FAILLOCK_FLAG_SILENT; - if (opts->dir == NULL) { - pam_syslog(pamh, LOG_CRIT, "Error allocating memory: %m"); - opts->fatal_error = 1; - } -} - -/* parse a single configuration file */ -int -read_config_file(pam_handle_t *pamh, struct options *opts, const char *cfgfile) -{ - FILE *f; - char linebuf[FAILLOCK_CONF_MAX_LINELEN+1]; - - f = fopen(cfgfile, "r"); - if (f == NULL) { - /* ignore non-existent default config file */ - if (errno == ENOENT && strcmp(cfgfile, FAILLOCK_DEFAULT_CONF) == 0) - return 0; - return FAILLOCK_ERROR_CONF_OPEN; - } - - while (fgets(linebuf, sizeof(linebuf), f) != NULL) { - size_t len; - char *ptr; - char *name; - int eq; - - len = strlen(linebuf); - /* len cannot be 0 unless there is a bug in fgets */ - if (len && linebuf[len - 1] != '\n' && !feof(f)) { - (void) fclose(f); - return FAILLOCK_ERROR_CONF_MALFORMED; - } - - if ((ptr=strchr(linebuf, '#')) != NULL) { - *ptr = '\0'; - } else { - ptr = linebuf + len; - } - - /* drop terminating whitespace including the \n */ - while (ptr > linebuf) { - if (!isspace(*(ptr-1))) { - *ptr = '\0'; - break; - } - --ptr; - } - - /* skip initial whitespace */ - for (ptr = linebuf; isspace(*ptr); ptr++); - if (*ptr == '\0') - continue; - - /* grab the key name */ - eq = 0; - name = ptr; - while (*ptr != '\0') { - if (isspace(*ptr) || *ptr == '=') { - eq = *ptr == '='; - *ptr = '\0'; - ++ptr; - break; - } - ++ptr; - } - - /* grab the key value */ - while (*ptr != '\0') { - if (*ptr != '=' || eq) { - if (!isspace(*ptr)) { - break; - } - } else { - eq = 1; - } - ++ptr; - } - - /* set the key:value pair on opts */ - set_conf_opt(pamh, opts, name, ptr); - } - - (void)fclose(f); + if (opts->fatal_error) + return PAM_BUF_ERR; return PAM_SUCCESS; } -void set_conf_opt(pam_handle_t *pamh, struct options *opts, const char *name, const char *value) -{ - if (strcmp(name, "dir") == 0) { - if (value[0] != '/') { - pam_syslog(pamh, LOG_ERR, - "Tally directory is not absolute path (%s); keeping default", value); - } else { - free(opts->dir); - opts->dir = strdup(value); - } - } - else if (strcmp(name, "deny") == 0) { - if (sscanf(value, "%hu", &opts->deny) != 1) { - pam_syslog(pamh, LOG_ERR, - "Bad number supplied for deny argument"); - } - } - else if (strcmp(name, "fail_interval") == 0) { - unsigned int temp; - if (sscanf(value, "%u", &temp) != 1 || - temp > MAX_TIME_INTERVAL) { - pam_syslog(pamh, LOG_ERR, - "Bad number supplied for fail_interval argument"); - } else { - opts->fail_interval = temp; - } - } - else if (strcmp(name, "unlock_time") == 0) { - unsigned int temp; - - if (strcmp(value, "never") == 0) { - opts->unlock_time = 0; - } - else if (sscanf(value, "%u", &temp) != 1 || - temp > MAX_TIME_INTERVAL) { - pam_syslog(pamh, LOG_ERR, - "Bad number supplied for unlock_time argument"); - } - else { - opts->unlock_time = temp; - } - } - else if (strcmp(name, "root_unlock_time") == 0) { - unsigned int temp; - - if (strcmp(value, "never") == 0) { - opts->root_unlock_time = 0; - } - else if (sscanf(value, "%u", &temp) != 1 || - temp > MAX_TIME_INTERVAL) { - pam_syslog(pamh, LOG_ERR, - "Bad number supplied for root_unlock_time argument"); - } else { - opts->root_unlock_time = temp; - } - } - else if (strcmp(name, "admin_group") == 0) { - free(opts->admin_group); - opts->admin_group = strdup(value); - if (opts->admin_group == NULL) { - opts->fatal_error = 1; - pam_syslog(pamh, LOG_CRIT, "Error allocating memory: %m"); - } - } - else if (strcmp(name, "even_deny_root") == 0) { - opts->flags |= FAILLOCK_FLAG_DENY_ROOT; - } - else if (strcmp(name, "audit") == 0) { - opts->flags |= FAILLOCK_FLAG_AUDIT; - } - else if (strcmp(name, "silent") == 0) { - opts->flags |= FAILLOCK_FLAG_SILENT; - } - else if (strcmp(name, "no_log_info") == 0) { - opts->flags |= FAILLOCK_FLAG_NO_LOG_INFO; - } - else if (strcmp(name, "local_users_only") == 0) { - opts->flags |= FAILLOCK_FLAG_LOCAL_ONLY; - } - else { - pam_syslog(pamh, LOG_ERR, "Unknown option: %s", name); - } -} - -static int check_local_user (pam_handle_t *pamh, const char *user) +static int +check_local_user (pam_handle_t *pamh, const char *user) { - struct passwd pw, *pwp; - char buf[4096]; - int found = 0; - FILE *fp; - int errn; - - fp = fopen(PATH_PASSWD, "r"); - if (fp == NULL) { - pam_syslog(pamh, LOG_ERR, "unable to open %s: %m", - PATH_PASSWD); - return -1; - } - - for (;;) { - errn = fgetpwent_r(fp, &pw, buf, sizeof (buf), &pwp); - if (errn == ERANGE) { - pam_syslog(pamh, LOG_WARNING, "%s contains very long lines; corrupted?", - PATH_PASSWD); - /* we can continue here as next call will read further */ - continue; - } - if (errn != 0) - break; - if (strcmp(pwp->pw_name, user) == 0) { - found = 1; - break; - } - } - - fclose (fp); - - if (errn != 0 && errn != ENOENT) { - pam_syslog(pamh, LOG_ERR, "unable to enumerate local accounts: %m"); - return -1; - } else { - return found; - } + return pam_modutil_check_user_in_passwd(pamh, user, NULL) == PAM_SUCCESS; } -static int get_pam_user(pam_handle_t *pamh, struct options *opts) +static int +get_pam_user(pam_handle_t *pamh, struct options *opts) { const char *user; int rv; struct passwd *pwd; if ((rv=pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) { - return rv; + return rv == PAM_CONV_AGAIN ? PAM_INCOMPLETE : rv; } if (*user == '\0') { @@ -391,10 +157,10 @@ static int get_pam_user(pam_handle_t *pa if ((pwd=pam_modutil_getpwnam(pamh, user)) == NULL) { if (opts->flags & FAILLOCK_FLAG_AUDIT) { - pam_syslog(pamh, LOG_ERR, "User unknown: %s", user); + pam_syslog(pamh, LOG_NOTICE, "User unknown: %s", user); } else { - pam_syslog(pamh, LOG_ERR, "User unknown"); + pam_syslog(pamh, LOG_NOTICE, "User unknown"); } return PAM_IGNORE; } @@ -421,10 +187,11 @@ check_tally(pam_handle_t *pamh, struct o unsigned int i; uint64_t latest_time; int failures; + const char *dir = get_tally_dir(opts); opts->now = time(NULL); - tfd = open_tally(opts->dir, opts->user, opts->uid, 0); + tfd = open_tally(dir, opts->user, opts->uid, 0); *fd = tfd; @@ -446,7 +213,7 @@ check_tally(pam_handle_t *pamh, struct o } latest_time = 0; - for(i = 0; i < tallies->count; i++) { + for (i = 0; i < tallies->count; i++) { if ((tallies->records[i].status & TALLY_STATUS_VALID) && tallies->records[i].time > latest_time) latest_time = tallies->records[i].time; @@ -455,7 +222,7 @@ check_tally(pam_handle_t *pamh, struct o opts->latest_time = latest_time; failures = 0; - for(i = 0; i < tallies->count; i++) { + for (i = 0; i < tallies->count; i++) { if ((tallies->records[i].status & TALLY_STATUS_VALID) && latest_time - tallies->records[i].time < opts->fail_interval) { ++failures; @@ -498,9 +265,10 @@ static void reset_tally(pam_handle_t *pamh, struct options *opts, int *fd) { int rv; + const char *dir = get_tally_dir(opts); if (*fd == -1) { - *fd = open_tally(opts->dir, opts->user, opts->uid, 1); + *fd = open_tally(dir, opts->user, opts->uid, 1); } else { while ((rv=ftruncate(*fd, 0)) == -1 && errno == EINTR); @@ -519,9 +287,10 @@ write_tally(pam_handle_t *pamh, struct o unsigned int oldest; uint64_t oldtime; const void *source = NULL; + const char *dir = get_tally_dir(opts); if (*fd == -1) { - *fd = open_tally(opts->dir, opts->user, opts->uid, 1); + *fd = open_tally(dir, opts->user, opts->uid, 1); } if (*fd == -1) { if (errno == EACCES) { @@ -536,7 +305,7 @@ write_tally(pam_handle_t *pamh, struct o failures = 0; for (i = 0; i < tallies->count; ++i) { - if (tallies->records[i].time < oldtime) { + if (oldtime == 0 || tallies->records[i].time < oldtime) { oldtime = tallies->records[i].time; oldest = i; } @@ -630,16 +399,26 @@ faillock_message(pam_handle_t *pamh, str left = opts->latest_time + opts->unlock_time - opts->now; } + pam_info(pamh, _("The account is locked due to %u failed logins."), + (unsigned int)opts->failures); if (left > 0) { left = (left + 59)/60; /* minutes */ - pam_info(pamh, _("Account temporarily locked due to %d failed logins"), - opts->failures); - pam_info(pamh, _("(%d minutes left to unlock)"), (int)left); - } - else { - pam_info(pamh, _("Account locked due to %d failed logins"), - opts->failures); +#if defined HAVE_DNGETTEXT && defined ENABLE_NLS + pam_info( + pamh, + dngettext(PACKAGE, + "(%d minute left to unlock)", + "(%d minutes left to unlock)", + (int)left), + (int)left); +#else + if (left == 1) + pam_info(pamh, _("(%d minute left to unlock)"), (int)left); + else + /* TRANSLATORS: only used if dngettext is not supported. */ + pam_info(pamh, _("(%d minutes left to unlock)"), (int)left); +#endif } } } @@ -663,7 +442,7 @@ opts_cleanup(struct options *opts) /*---------------------------------------------------------------------*/ -PAM_EXTERN int +int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { @@ -673,13 +452,13 @@ pam_sm_authenticate(pam_handle_t *pamh, memset(&tallies, 0, sizeof(tallies)); - args_parse(pamh, argc, argv, flags, &opts); - if (opts.fatal_error) { - rv = PAM_BUF_ERR; + rv = args_parse(pamh, argc, argv, flags, &opts); + if (rv != PAM_SUCCESS) goto err; - } - pam_fail_delay(pamh, 2000000); /* 2 sec delay for on failure */ + if (!(opts.flags & FAILLOCK_FLAG_NO_DELAY)) { + pam_fail_delay(pamh, 2000000); /* 2 sec delay on failure */ + } if ((rv=get_pam_user(pamh, &opts)) != PAM_SUCCESS) { goto err; @@ -722,7 +501,7 @@ err: /*---------------------------------------------------------------------*/ -PAM_EXTERN int +int pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) { @@ -731,7 +510,7 @@ pam_sm_setcred(pam_handle_t *pamh UNUSED /*---------------------------------------------------------------------*/ -PAM_EXTERN int +int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { @@ -741,12 +520,10 @@ pam_sm_acct_mgmt(pam_handle_t *pamh, int memset(&tallies, 0, sizeof(tallies)); - args_parse(pamh, argc, argv, flags, &opts); + rv = args_parse(pamh, argc, argv, flags, &opts); - if (opts.fatal_error) { - rv = PAM_BUF_ERR; + if (rv != PAM_SUCCESS) goto err; - } opts.action = FAILLOCK_ACTION_AUTHSUCC; @@ -770,28 +547,3 @@ err: /*-----------------------------------------------------------------------*/ -#ifdef PAM_STATIC - -/* static module data */ - -struct pam_module _pam_faillock_modstruct = { - MODULE_NAME, -#ifdef PAM_SM_AUTH - pam_sm_authenticate, - pam_sm_setcred, -#else - NULL, - NULL, -#endif -#ifdef PAM_SM_ACCOUNT - pam_sm_acct_mgmt, -#else - NULL, -#endif - NULL, - NULL, - NULL, -}; - -#endif /* #ifdef PAM_STATIC */ - diff --git a/modules/pam_faillock/faillock.h b/modules/pam_faillock/faillock.h index b22a9dfb..0ea0ffba 100644 --- a/modules/pam_faillock/faillock.h +++ b/modules/pam_faillock/faillock.h diff -up Linux-PAM-1.5.1/modules/pam_faillock/faillock.h.faillock-load-conf-from-file Linux-PAM-1.5.1/modules/pam_faillock/faillock.h --- Linux-PAM-1.5.1/modules/pam_faillock/faillock.h.faillock-load-conf-from-file 2020-11-25 17:57:02.000000000 +0100 +++ Linux-PAM-1.5.1/modules/pam_faillock/faillock.h 2022-05-25 15:33:03.885551825 +0200 @@ -67,7 +67,6 @@ struct tally_data { }; #define FAILLOCK_DEFAULT_TALLYDIR "/var/run/faillock" -#define FAILLOCK_DEFAULT_CONF "/etc/security/faillock.conf" int open_tally(const char *dir, const char *user, uid_t uid, int create); int read_tally(int fd, struct tally_data *tallies); diff --git a/modules/pam_faillock/pam_faillock.c b/modules/pam_faillock/pam_faillock.c index ddbb90e7..f46fca99 100644 --- a/modules/pam_faillock/pam_faillock.c +++ b/modules/pam_faillock/pam_faillock.c @@ -134,10 +134,96 @@ args_parse(pam_handle_t *pamh, int argc, const char **argv, return PAM_SUCCESS; } +static int +check_user_in_passwd(pam_handle_t *pamh, + const char *user_name, + const char *file_name) +{ + int rc; + size_t user_len; + FILE *fp; + char line[BUFSIZ]; + + /* Validate the user name. */ + if ((user_len = strlen(user_name)) == 0) { + pam_syslog(pamh, LOG_NOTICE, "user name is not valid"); + return PAM_SERVICE_ERR; + } + + if (user_len > sizeof(line) - sizeof(":")) { + pam_syslog(pamh, LOG_NOTICE, "user name is too long"); + return PAM_SERVICE_ERR; + } + + if (strchr(user_name, ':') != NULL) { + /* + * "root:x" is not a local user name even if the passwd file + * contains a line starting with "root:x:". + */ + return PAM_PERM_DENIED; + } + + /* Open the passwd file. */ + if (file_name == NULL) { + file_name = "/etc/passwd"; + } + if ((fp = fopen(file_name, "r")) == NULL) { + pam_syslog(pamh, LOG_ERR, "error opening %s: %m", file_name); + return PAM_SERVICE_ERR; + } + + /* + * Scan the file using fgets() instead of fgetpwent_r() because + * the latter is not flexible enough in handling long lines + * in passwd files. + */ + rc = PAM_PERM_DENIED; + while (fgets(line, sizeof(line), fp) != NULL) { + size_t line_len; + const char *str; + + /* + * Does this line start with the user name + * followed by a colon? + */ + if (strncmp(user_name, line, user_len) == 0 && + line[user_len] == ':') { + rc = PAM_SUCCESS; + /* + * Continue reading the file to avoid timing attacks. + */ + } + /* Has a newline been read? */ + line_len = strlen(line); + if (line_len < sizeof(line) - 1 || + line[line_len - 1] == '\n') { + /* Yes, continue with the next line. */ + continue; + } + + /* No, read till the end of this line first. */ + while ((str = fgets(line, sizeof(line), fp)) != NULL) { + line_len = strlen(line); + if (line_len == 0 || + line[line_len - 1] == '\n') { + break; + } + } + if (str == NULL) { + /* fgets returned NULL, we are done. */ + break; + } + /* Continue with the next line. */ + } + + fclose(fp); + return rc; +} + static int check_local_user (pam_handle_t *pamh, const char *user) { - return pam_modutil_check_user_in_passwd(pamh, user, NULL) == PAM_SUCCESS; + return check_user_in_passwd(pamh, user, NULL) == PAM_SUCCESS; } static int