diff --git a/SOURCES/pam-1.3.1-faillock-load-conf-from-file.patch b/SOURCES/pam-1.3.1-faillock-load-conf-from-file.patch
new file mode 100644
index 0000000..81ea7cc
--- /dev/null
+++ b/SOURCES/pam-1.3.1-faillock-load-conf-from-file.patch
@@ -0,0 +1,1212 @@
+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
diff --git a/SOURCES/pam-1.3.1-inline.patch b/SOURCES/pam-1.3.1-inline.patch
new file mode 100644
index 0000000..65dfa26
--- /dev/null
+++ b/SOURCES/pam-1.3.1-inline.patch
@@ -0,0 +1,105 @@
+diff -up Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h.inline Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h
+--- Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h.inline 2022-05-26 10:44:31.702623614 +0200
++++ Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h 2022-05-26 10:44:31.703623621 +0200
+@@ -44,4 +44,17 @@
+ # define DIAG_POP_IGNORE_CAST_ALIGN /* empty */
+ #endif
+
++/*
++ * Evaluates to
++ * 1, if the given two types are known to be the same
++ * 0, otherwise.
++ */
++#if PAM_GNUC_PREREQ(3, 0)
++# define PAM_IS_SAME_TYPE(x_, y_) \
++ __builtin_types_compatible_p(__typeof__(x_), __typeof__(y_))
++#else
++/* Cannot tell whether these types are the same. */
++# define PAM_IS_SAME_TYPE(x_, y_) 0
++#endif
++
+ #endif /* PAM_CC_COMPAT_H */
+diff -up Linux-PAM-1.3.1/libpam/include/pam_inline.h.inline Linux-PAM-1.3.1/libpam/include/pam_inline.h
+--- Linux-PAM-1.3.1/libpam/include/pam_inline.h.inline 2022-05-26 10:44:31.703623621 +0200
++++ Linux-PAM-1.3.1/libpam/include/pam_inline.h 2022-05-26 10:44:31.703623621 +0200
+@@ -0,0 +1,67 @@
++/*
++ * Copyright (c) 2020 Dmitry V. Levin
++ *
++ * Handy inline functions and macros providing some convenient functionality
++ * to libpam and its modules.
++ */
++
++#ifndef PAM_INLINE_H
++#define PAM_INLINE_H
++
++#include "pam_cc_compat.h"
++#include
++
++/*
++ * Evaluates to
++ * - a syntax error if the argument is 0,
++ * 0, otherwise.
++ */
++#define PAM_FAIL_BUILD_ON_ZERO(e_) (sizeof(int[-1 + 2 * !!(e_)]) * 0)
++
++/*
++ * Evaluates to
++ * 1, if the given type is known to be a non-array type
++ * 0, otherwise.
++ */
++#define PAM_IS_NOT_ARRAY(a_) PAM_IS_SAME_TYPE((a_), &(a_)[0])
++
++/*
++ * Evaluates to
++ * - a syntax error if the argument is not an array,
++ * 0, otherwise.
++ */
++#define PAM_MUST_BE_ARRAY(a_) PAM_FAIL_BUILD_ON_ZERO(!PAM_IS_NOT_ARRAY(a_))
++
++/* Evaluates to the number of elements in the specified array. */
++#define PAM_ARRAY_SIZE(a_) (sizeof(a_) / sizeof((a_)[0]) + PAM_MUST_BE_ARRAY(a_))
++
++/*
++ * Returns NULL if STR does not start with PREFIX,
++ * or a pointer to the first char in STR after PREFIX.
++ * The length of PREFIX is specified by PREFIX_LEN.
++ */
++static inline const char *
++pam_str_skip_prefix_len(const char *str, const char *prefix, size_t prefix_len)
++{
++ return strncmp(str, prefix, prefix_len) ? NULL : str + prefix_len;
++}
++
++#define pam_str_skip_prefix(str_, prefix_) \
++ pam_str_skip_prefix_len((str_), (prefix_), sizeof(prefix_) - 1 + PAM_MUST_BE_ARRAY(prefix_))
++
++/*
++ * Returns NULL if STR does not start with PREFIX
++ * (ignoring the case of the characters),
++ * or a pointer to the first char in STR after PREFIX.
++ * The length of PREFIX is specified by PREFIX_LEN.
++ */
++static inline const char *
++pam_str_skip_icase_prefix_len(const char *str, const char *prefix, size_t prefix_len)
++{
++ return strncasecmp(str, prefix, prefix_len) ? NULL : str + prefix_len;
++}
++
++#define pam_str_skip_icase_prefix(str_, prefix_) \
++ pam_str_skip_icase_prefix_len((str_), (prefix_), sizeof(prefix_) - 1 + PAM_MUST_BE_ARRAY(prefix_))
++
++#endif /* PAM_INLINE_H */
+diff -up Linux-PAM-1.3.1/libpam/Makefile.am.inline Linux-PAM-1.3.1/libpam/Makefile.am
+--- Linux-PAM-1.3.1/libpam/Makefile.am.inline 2022-05-26 10:44:31.702623614 +0200
++++ Linux-PAM-1.3.1/libpam/Makefile.am 2022-05-26 10:45:21.146977780 +0200
+@@ -18,7 +18,8 @@ include_HEADERS = include/security/_pam_
+ include/security/pam_ext.h include/security/pam_modutil.h
+
+ noinst_HEADERS = pam_prelude.h pam_private.h pam_tokens.h \
+- pam_modutil_private.h include/pam_cc_compat.h
++ pam_modutil_private.h include/pam_cc_compat.h \
++ include/pam_inline.h
+
+ libpam_la_LDFLAGS = -no-undefined -version-info 84:2:84
+ libpam_la_LIBADD = @LIBAUDIT@ $(LIBPRELUDE_LIBS) @LIBDL@
diff --git a/SOURCES/pam-1.3.1-pam-cc-compat.patch b/SOURCES/pam-1.3.1-pam-cc-compat.patch
new file mode 100644
index 0000000..fe9a95a
--- /dev/null
+++ b/SOURCES/pam-1.3.1-pam-cc-compat.patch
@@ -0,0 +1,63 @@
+diff -up Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h.pam-cc-compat Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h
+--- Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h.pam-cc-compat 2022-05-26 10:43:50.436328027 +0200
++++ Linux-PAM-1.3.1/libpam/include/pam_cc_compat.h 2022-05-26 10:43:50.436328027 +0200
+@@ -0,0 +1,47 @@
++/*
++ * Copyright (c) 2020 Dmitry V. Levin
++ */
++
++#ifndef PAM_CC_COMPAT_H
++#define PAM_CC_COMPAT_H
++
++#include "config.h"
++#include
++
++#if defined __clang__ && defined __clang_major__ && defined __clang_minor__
++# define PAM_CLANG_PREREQ(maj, min) \
++ ((__clang_major__ << 16) + __clang_minor__ >= ((maj) << 16) + (min))
++#else
++# define PAM_CLANG_PREREQ(maj, min) 0
++#endif
++
++#if PAM_GNUC_PREREQ(4, 6)
++# define DIAG_PUSH_IGNORE_CAST_QUAL \
++ _Pragma("GCC diagnostic push"); \
++ _Pragma("GCC diagnostic ignored \"-Wcast-qual\"")
++# define DIAG_POP_IGNORE_CAST_QUAL \
++ _Pragma("GCC diagnostic pop")
++# define DIAG_PUSH_IGNORE_CAST_ALIGN \
++ _Pragma("GCC diagnostic push"); \
++ _Pragma("GCC diagnostic ignored \"-Wcast-align\"")
++# define DIAG_POP_IGNORE_CAST_ALIGN \
++ _Pragma("GCC diagnostic pop")
++#elif PAM_CLANG_PREREQ(2, 6)
++# define DIAG_PUSH_IGNORE_CAST_QUAL \
++ _Pragma("clang diagnostic push"); \
++ _Pragma("clang diagnostic ignored \"-Wcast-qual\"")
++# define DIAG_POP_IGNORE_CAST_QUAL \
++ _Pragma("clang diagnostic pop")
++# define DIAG_PUSH_IGNORE_CAST_ALIGN \
++ _Pragma("clang diagnostic push"); \
++ _Pragma("clang diagnostic ignored \"-Wcast-align\"")
++# define DIAG_POP_IGNORE_CAST_ALIGN \
++ _Pragma("clang diagnostic pop")
++#else
++# define DIAG_PUSH_IGNORE_CAST_QUAL /* empty */
++# define DIAG_POP_IGNORE_CAST_QUAL /* empty */
++# define DIAG_PUSH_IGNORE_CAST_ALIGN /* empty */
++# define DIAG_POP_IGNORE_CAST_ALIGN /* empty */
++#endif
++
++#endif /* PAM_CC_COMPAT_H */
+diff -up Linux-PAM-1.3.1/libpam/Makefile.am.pam-cc-compat Linux-PAM-1.3.1/libpam/Makefile.am
+--- Linux-PAM-1.3.1/libpam/Makefile.am.pam-cc-compat 2022-05-26 10:43:50.436328027 +0200
++++ Linux-PAM-1.3.1/libpam/Makefile.am 2022-05-26 10:44:00.865402730 +0200
+@@ -18,7 +18,7 @@ include_HEADERS = include/security/_pam_
+ include/security/pam_ext.h include/security/pam_modutil.h
+
+ noinst_HEADERS = pam_prelude.h pam_private.h pam_tokens.h \
+- pam_modutil_private.h
++ pam_modutil_private.h include/pam_cc_compat.h
+
+ libpam_la_LDFLAGS = -no-undefined -version-info 84:2:84
+ libpam_la_LIBADD = @LIBAUDIT@ $(LIBPRELUDE_LIBS) @LIBDL@
diff --git a/SOURCES/pam-1.3.1-pam-keyinit-thread-safe.patch b/SOURCES/pam-1.3.1-pam-keyinit-thread-safe.patch
new file mode 100644
index 0000000..1f322a1
--- /dev/null
+++ b/SOURCES/pam-1.3.1-pam-keyinit-thread-safe.patch
@@ -0,0 +1,125 @@
+diff -up Linux-PAM-1.3.1/modules/pam_keyinit/pam_keyinit.c.pam_keyinit-thread-safe Linux-PAM-1.3.1/modules/pam_keyinit/pam_keyinit.c
+--- Linux-PAM-1.3.1/modules/pam_keyinit/pam_keyinit.c.pam_keyinit-thread-safe 2017-02-10 11:10:15.000000000 +0100
++++ Linux-PAM-1.3.1/modules/pam_keyinit/pam_keyinit.c 2022-04-25 12:10:28.071240439 +0200
+@@ -20,6 +20,7 @@
+ #include
+ #include
+ #include
++#include
+
+ #define KEY_SPEC_SESSION_KEYRING -3 /* ID for session keyring */
+ #define KEY_SPEC_USER_KEYRING -4 /* ID for UID-specific keyring */
+@@ -30,12 +31,12 @@
+ #define KEYCTL_REVOKE 3 /* revoke a key */
+ #define KEYCTL_LINK 8 /* link a key into a keyring */
+
+-static int my_session_keyring;
+-static int session_counter;
+-static int do_revoke;
+-static int revoke_as_uid;
+-static int revoke_as_gid;
+-static int xdebug = 0;
++static _Thread_local int my_session_keyring = 0;
++static _Atomic int session_counter = 0;
++static _Thread_local int do_revoke = 0;
++static _Thread_local uid_t revoke_as_uid;
++static _Thread_local gid_t revoke_as_gid;
++static _Thread_local int xdebug = 0;
+
+ static void debug(pam_handle_t *pamh, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+@@ -65,6 +66,33 @@ static int error(pam_handle_t *pamh, con
+ return PAM_SESSION_ERR;
+ }
+
++static int pam_setreuid(uid_t ruid, uid_t euid)
++{
++#if defined(SYS_setreuid32)
++ return syscall(SYS_setreuid32, ruid, euid);
++#else
++ return syscall(SYS_setreuid, ruid, euid);
++#endif
++}
++
++static int pam_setregid(gid_t rgid, gid_t egid)
++{
++#if defined(SYS_setregid32)
++ return syscall(SYS_setregid32, rgid, egid);
++#else
++ return syscall(SYS_setregid, rgid, egid);
++#endif
++}
++
++static int pam_setresuid(uid_t ruid, uid_t euid, uid_t suid)
++{
++#if defined(SYS_setresuid32)
++ return syscall(SYS_setresuid32, ruid, euid, suid);
++#else
++ return syscall(SYS_setresuid, ruid, euid, suid);
++#endif
++}
++
+ /*
+ * initialise the session keyring for this process
+ */
+@@ -139,23 +167,25 @@ static void kill_keyrings(pam_handle_t *
+
+ /* switch to the real UID and GID so that we have permission to
+ * revoke the key */
+- if (revoke_as_gid != old_gid && setregid(-1, revoke_as_gid) < 0)
++ if (revoke_as_gid != old_gid && pam_setregid(-1, revoke_as_gid) < 0)
+ error(pamh, "Unable to change GID to %d temporarily\n",
+ revoke_as_gid);
+
+- if (revoke_as_uid != old_uid && setresuid(-1, revoke_as_uid, old_uid) < 0)
++ if (revoke_as_uid != old_uid && pam_setresuid(-1, revoke_as_uid, old_uid) < 0)
+ error(pamh, "Unable to change UID to %d temporarily\n",
+ revoke_as_uid);
++ if (getegid() != old_gid && pam_setregid(-1, old_gid) < 0)
++ error(pamh, "Unable to change GID back to %d\n", old_gid);
+
+ syscall(__NR_keyctl,
+ KEYCTL_REVOKE,
+ my_session_keyring);
+
+ /* return to the orignal UID and GID (probably root) */
+- if (revoke_as_uid != old_uid && setreuid(-1, old_uid) < 0)
++ if (revoke_as_uid != old_uid && pam_setreuid(-1, old_uid) < 0)
+ error(pamh, "Unable to change UID back to %d\n", old_uid);
+
+- if (revoke_as_gid != old_gid && setregid(-1, old_gid) < 0)
++ if (revoke_as_gid != old_gid && pam_setregid(-1, old_gid) < 0)
+ error(pamh, "Unable to change GID back to %d\n", old_gid);
+
+ my_session_keyring = 0;
+@@ -210,14 +240,14 @@ int pam_sm_open_session(pam_handle_t *pa
+
+ /* switch to the real UID and GID so that the keyring ends up owned by
+ * the right user */
+- if (gid != old_gid && setregid(gid, -1) < 0) {
++ if (gid != old_gid && pam_setregid(gid, -1) < 0) {
+ error(pamh, "Unable to change GID to %d temporarily\n", gid);
+ return PAM_SESSION_ERR;
+ }
+
+- if (uid != old_uid && setreuid(uid, -1) < 0) {
++ if (uid != old_uid && pam_setreuid(uid, -1) < 0) {
+ error(pamh, "Unable to change UID to %d temporarily\n", uid);
+- if (setregid(old_gid, -1) < 0)
++ if (pam_setregid(old_gid, -1) < 0)
+ error(pamh, "Unable to change GID back to %d\n", old_gid);
+ return PAM_SESSION_ERR;
+ }
+@@ -225,10 +255,10 @@ int pam_sm_open_session(pam_handle_t *pa
+ ret = init_keyrings(pamh, force);
+
+ /* return to the orignal UID and GID (probably root) */
+- if (uid != old_uid && setreuid(old_uid, -1) < 0)
++ if (uid != old_uid && pam_setreuid(old_uid, -1) < 0)
+ ret = error(pamh, "Unable to change UID back to %d\n", old_uid);
+
+- if (gid != old_gid && setregid(old_gid, -1) < 0)
++ if (gid != old_gid && pam_setregid(old_gid, -1) < 0)
+ ret = error(pamh, "Unable to change GID back to %d\n", old_gid);
+
+ return ret;
diff --git a/SOURCES/pam-1.3.1-pam-motd-fix-memory-leak.patch b/SOURCES/pam-1.3.1-pam-motd-fix-memory-leak.patch
new file mode 100644
index 0000000..932633b
--- /dev/null
+++ b/SOURCES/pam-1.3.1-pam-motd-fix-memory-leak.patch
@@ -0,0 +1,57 @@
+From 62cd745d730e5ba13d5d7092ac566fc0b2148e61 Mon Sep 17 00:00:00 2001
+From: "Dmitry V. Levin"
+Date: Sun, 26 Apr 2020 11:12:59 +0000
+Subject: [PATCH] pam_motd: fix memory leak
+
+pam_motd used to leak memory allocated for each motd file
+successfully opened in try_to_display_directories_with_overrides.
+
+* modules/pam_motd/pam_motd.c
+(try_to_display_directories_with_overrides): Free abs_path.
+
+Fixes: f9c9c721 ("pam_motd: Support multiple motd paths specified, with filename overrides (#69)")
+---
+ modules/pam_motd/pam_motd.c | 20 ++++++++++----------
+ 1 file changed, 10 insertions(+), 10 deletions(-)
+
+diff --git a/modules/pam_motd/pam_motd.c b/modules/pam_motd/pam_motd.c
+index f0cd317d..3be129a5 100644
+--- a/modules/pam_motd/pam_motd.c
++++ b/modules/pam_motd/pam_motd.c
+@@ -259,23 +259,23 @@ static void try_to_display_directories_with_overrides(pam_handle_t *pamh,
+
+ for (j = 0; j < num_motd_dirs; j++) {
+ char *abs_path = NULL;
++ int fd;
+
+ if (join_dir_strings(&abs_path, motd_dir_path_split[j],
+- dirnames_all[i]) < 0) {
++ dirnames_all[i]) < 0 || abs_path == NULL) {
+ continue;
+ }
+
+- if (abs_path != NULL) {
+- int fd = open(abs_path, O_RDONLY, 0);
+- if (fd >= 0) {
+- try_to_display_fd(pamh, fd);
+- close(fd);
++ fd = open(abs_path, O_RDONLY, 0);
++ _pam_drop(abs_path);
+
+- /* We displayed a file, skip to the next file name. */
+- break;
+- }
++ if (fd >= 0) {
++ try_to_display_fd(pamh, fd);
++ close(fd);
++
++ /* We displayed a file, skip to the next file name. */
++ break;
+ }
+- _pam_drop(abs_path);
+ }
+ }
+
+--
+2.35.3
+
diff --git a/SOURCES/pam-1.3.1-pam-motd-fix-segmentation-fault.patch b/SOURCES/pam-1.3.1-pam-motd-fix-segmentation-fault.patch
new file mode 100644
index 0000000..af72866
--- /dev/null
+++ b/SOURCES/pam-1.3.1-pam-motd-fix-segmentation-fault.patch
@@ -0,0 +1,133 @@
+From 8eaf5570cf011148a0b55c53570df5edaafebdb0 Mon Sep 17 00:00:00 2001
+From: Robert Fairley
+Date: Wed, 21 Nov 2018 02:46:02 -0500
+Subject: [PATCH] pam_motd: Fix segmentation fault when no motd_dir specified
+ (#76)
+
+This fixes a regression introduced by #69, where motd_path was set
+to NULL and passed into strdup() if the motd_dir argument was
+not specified in the configuration file. This caused a segmentation
+fault.
+
+* modules/pam_motd/pam_motd.c: fix checks for NULL in arguments
+* xtests/Makefile.am: add test scripts and config file
+* xtests/tst-pam_motd.sh: add running tst-pam_motd4.sh
+* xtests/tst-pam_motd4.pamd: create
+* xtests/tst-pam_motd4.sh: create
+---
+ modules/pam_motd/pam_motd.c | 15 ++++++++++-----
+ xtests/Makefile.am | 4 ++--
+ xtests/tst-pam_motd.sh | 1 +
+ xtests/tst-pam_motd4.pamd | 3 +++
+ xtests/tst-pam_motd4.sh | 27 +++++++++++++++++++++++++++
+ 5 files changed, 43 insertions(+), 7 deletions(-)
+ create mode 100644 xtests/tst-pam_motd4.pamd
+ create mode 100755 xtests/tst-pam_motd4.sh
+
+diff --git a/modules/pam_motd/pam_motd.c b/modules/pam_motd/pam_motd.c
+index 1c1cfcfa..ec3ebd58 100644
+--- a/modules/pam_motd/pam_motd.c
++++ b/modules/pam_motd/pam_motd.c
+@@ -132,7 +132,6 @@ static int pam_split_string(const pam_handle_t *pamh, char *arg, char delim,
+ goto out;
+ }
+
+-
+ arg_extracted = strtok_r(arg, delim_str, &arg);
+ while (arg_extracted != NULL && i < num_strs) {
+ arg_split[i++] = arg_extracted;
+@@ -363,15 +362,21 @@ int pam_sm_open_session(pam_handle_t *pamh, int flags,
+ motd_dir_path = default_motd_dir;
+ }
+
+- motd_path_copy = strdup(motd_path);
++ if (motd_path != NULL) {
++ motd_path_copy = strdup(motd_path);
++ }
++
+ if (motd_path_copy != NULL) {
+- if (pam_split_string(pamh, motd_path_copy, ':', &motd_path_split,
+- &num_motd_paths) == 0) {
++ if (pam_split_string(pamh, motd_path_copy, ':',
++ &motd_path_split, &num_motd_paths) == 0) {
+ goto out;
+ }
+ }
+
+- motd_dir_path_copy = strdup(motd_dir_path);
++ if (motd_dir_path != NULL) {
++ motd_dir_path_copy = strdup(motd_dir_path);
++ }
++
+ if (motd_dir_path_copy != NULL) {
+ if (pam_split_string(pamh, motd_dir_path_copy, ':',
+ &motd_dir_path_split, &num_motd_dir_paths) == 0) {
+diff --git a/xtests/Makefile.am b/xtests/Makefile.am
+index 555d5e33..4d5aba3d 100644
+--- a/xtests/Makefile.am
++++ b/xtests/Makefile.am
+@@ -34,8 +34,8 @@ EXTRA_DIST = run-xtests.sh tst-pam_dispatch1.pamd tst-pam_dispatch2.pamd \
+ tst-pam_pwhistory1.pamd tst-pam_pwhistory1.sh \
+ tst-pam_time1.pamd time.conf \
+ tst-pam_motd.sh tst-pam_motd1.sh tst-pam_motd2.sh \
+- tst-pam_motd3.sh tst-pam_motd1.pamd \
+- tst-pam_motd2.pamd tst-pam_motd3.pamd
++ tst-pam_motd3.sh tst-pam_motd4.sh tst-pam_motd1.pamd \
++ tst-pam_motd2.pamd tst-pam_motd3.pamd tst-pam_motd4.pamd
+
+ XTESTS = tst-pam_dispatch1 tst-pam_dispatch2 tst-pam_dispatch3 \
+ tst-pam_dispatch4 tst-pam_dispatch5 \
+diff --git a/xtests/tst-pam_motd.sh b/xtests/tst-pam_motd.sh
+index 9b0c38f6..90801280 100755
+--- a/xtests/tst-pam_motd.sh
++++ b/xtests/tst-pam_motd.sh
+@@ -5,3 +5,4 @@ set -e
+ ./tst-pam_motd1.sh
+ ./tst-pam_motd2.sh
+ ./tst-pam_motd3.sh
++./tst-pam_motd4.sh
+diff --git a/xtests/tst-pam_motd4.pamd b/xtests/tst-pam_motd4.pamd
+new file mode 100644
+index 00000000..9dc311ad
+--- /dev/null
++++ b/xtests/tst-pam_motd4.pamd
+@@ -0,0 +1,3 @@
++#%PAM-1.0
++session required pam_permit.so
++session optional pam_motd.so motd=tst-pam_motd4.d/etc/motd
+diff --git a/xtests/tst-pam_motd4.sh b/xtests/tst-pam_motd4.sh
+new file mode 100755
+index 00000000..6022177f
+--- /dev/null
++++ b/xtests/tst-pam_motd4.sh
+@@ -0,0 +1,27 @@
++#!/bin/bash
++
++TST_DIR="tst-pam_motd4.d"
++
++function tst_cleanup() {
++ rm -rf "${TST_DIR}"
++ rm -f tst-pam_motd4.out
++}
++
++mkdir -p ${TST_DIR}/etc
++
++# Verify the case of single motd with no motd_dir given in tst-pam_motd4.pamd
++echo "motd: /etc/motd" > ${TST_DIR}/etc/motd
++
++./tst-pam_motd tst-pam_motd4 > tst-pam_motd4.out
++
++RET=$?
++
++motd_to_show_output=$(cat tst-pam_motd4.out | grep "motd: /etc/motd")
++if [ -z "${motd_to_show_output}" ];
++then
++ tst_cleanup
++ exit 1
++fi
++
++tst_cleanup
++exit $RET
+--
+2.35.1
+
diff --git a/SOURCES/pam-1.3.1-pam-motd-support-multiple-motd-paths.patch b/SOURCES/pam-1.3.1-pam-motd-support-multiple-motd-paths.patch
new file mode 100644
index 0000000..f81ecbe
--- /dev/null
+++ b/SOURCES/pam-1.3.1-pam-motd-support-multiple-motd-paths.patch
@@ -0,0 +1,691 @@
+diff -up Linux-PAM-1.3.1/modules/pam_motd/pam_motd.8.xml.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/modules/pam_motd/pam_motd.8.xml
+--- Linux-PAM-1.3.1/modules/pam_motd/pam_motd.8.xml.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.939663167 +0200
++++ Linux-PAM-1.3.1/modules/pam_motd/pam_motd.8.xml 2022-04-25 12:34:43.506582206 +0200
+@@ -21,6 +21,9 @@
+
+ motd=/path/filename
+
++
++ motd_dir=/path/dirname.d
++
+
+
+
+@@ -31,18 +34,49 @@
+
+ pam_motd is a PAM module that can be used to display
+ arbitrary motd (message of the day) files after a successful
+- login. By default the /etc/motd file and
+- all files from /etc/motd.d are
+- shown. The message size is limited to 64KB.
++ login. By default, pam_motd shows files in the
++ following locations:
++
++
++
++ /etc/motd
++ /run/motd
++ /usr/lib/motd
++ /etc/motd.d/
++ /run/motd.d/
++ /usr/lib/motd.d/
++
++
++
++ Each message size is limited to 64KB.
++
++
++ If /etc/motd does not exist,
++ then /run/motd is shown. If
++ /run/motd does not exist, then
++ /usr/lib/motd is shown.
++
++
++ Similar overriding behavior applies to the directories.
++ Files in /etc/motd.d/ override files
++ with the same name in /run/motd.d/ and
++ /usr/lib/motd.d/. Files in /run/motd.d/
++ override files with the same name in /usr/lib/motd.d/.
++
++
++ Files in the directories listed above are displayed in lexicographic
++ order by name. Moreover, the files are filtered by reading them with the
++ credentials of the target user authenticating on the system.
+
+
+ To silence a message,
+ a symbolic link with target /dev/null
+ may be placed in /etc/motd.d with
+ the same filename as the message to be silenced. Example:
++ Creating a symbolic link as follows silences /usr/lib/motd.d/my_motd.
+
+
+- ln -sfn /dev/null /etc/motd.d/my_motd
++ ln -s /dev/null /etc/motd.d/my_motd
+
+
+
+@@ -56,8 +90,10 @@
+
+
+
+- The /path/filename file is displayed
+- as message of the day.
++ The /path/filename file is displayed
++ as message of the day. Multiple paths to try can be
++ specified as a colon-separated list. By default this option
++ is set to /etc/motd:/run/motd:/usr/lib/motd.
+
+
+
+@@ -68,16 +104,17 @@
+
+
+ The /path/dirname.d directory is scanned
+- and each file contained inside of it is displayed.
++ and each file contained inside of it is displayed. Multiple
++ directories to scan can be specified as a colon-separated list.
++ By default this option is set to /etc/motd.d:/run/motd.d:/usr/lib/motd.d.
+
+
+
+
+
+- When no options are given, the default is to display both
+- /etc/motd and the contents of
+- /etc/motd.d. Specifying either option (or both)
+- will disable this default behavior.
++ When no options are given, the default behavior applies for both
++ options. Specifying either option (or both) will disable the
++ default behavior for both options.
+
+
+
+diff -up Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c
+--- Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c.pam_motd-support-multiple-motd-paths1 2018-05-18 11:50:46.000000000 +0200
++++ Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c 2022-04-25 12:32:36.947663225 +0200
+@@ -33,8 +33,8 @@
+ */
+
+ #define PAM_SM_SESSION
+-#define DEFAULT_MOTD "/etc/motd"
+-#define DEFAULT_MOTD_D "/etc/motd.d"
++#define DEFAULT_MOTD "/etc/motd:/run/motd:/usr/lib/motd"
++#define DEFAULT_MOTD_D "/etc/motd.d:/run/motd.d:/usr/lib/motd.d"
+
+ #include
+ #include
+@@ -97,12 +97,235 @@ static void try_to_display_directory(pam
+ }
+ }
+
++/*
++ * Split a DELIM-separated string ARG into an array.
++ * Outputs a newly allocated array of strings OUT_ARG_SPLIT
++ * and the number of strings OUT_NUM_STRS.
++ * Returns 0 in case of error, 1 in case of success.
++ */
++static int pam_split_string(const pam_handle_t *pamh, char *arg, char delim,
++ char ***out_arg_split, uint *out_num_strs)
++{
++ char *arg_extracted = NULL;
++ const char *arg_ptr = arg;
++ char **arg_split = NULL;
++ char delim_str[2];
++ int i = 0;
++ uint num_strs = 0;
++ int retval = 0;
++
++ delim_str[0] = delim;
++ delim_str[1] = '\0';
++
++ if (arg == NULL) {
++ goto out;
++ }
++
++ while (arg_ptr != NULL) {
++ num_strs++;
++ arg_ptr = strchr(arg_ptr + sizeof(const char), delim);
++ }
++
++ arg_split = (char **)calloc(num_strs, sizeof(char *));
++ if (arg_split == NULL) {
++ pam_syslog(pamh, LOG_CRIT, "pam_motd: failed to allocate string array");
++ goto out;
++ }
++
++
++ arg_extracted = strtok_r(arg, delim_str, &arg);
++ while (arg_extracted != NULL && i < num_strs) {
++ arg_split[i++] = arg_extracted;
++ arg_extracted = strtok_r(NULL, delim_str, &arg);
++ }
++
++ retval = 1;
++
++ out:
++ *out_num_strs = num_strs;
++ *out_arg_split = arg_split;
++
++ return retval;
++}
++
++/* Join A_STR and B_STR, inserting a "/" between them if one is not already trailing
++ * in A_STR or beginning B_STR. A pointer to a newly allocated string holding the
++ * joined string is returned in STRP_OUT.
++ * Returns -1 in case of error, or the number of bytes in the joined string in
++ * case of success. */
++static int join_dir_strings(char **strp_out, const char *a_str, const char *b_str)
++{
++ int has_sep = 0;
++ int retval = -1;
++ char *join_strp = NULL;
++
++ if (strp_out == NULL || a_str == NULL || b_str == NULL) {
++ goto out;
++ }
++ if (strlen(a_str) == 0) {
++ goto out;
++ }
++
++ has_sep = (a_str[strlen(a_str) - 1] == '/') || (b_str[0] == '/');
++
++ retval = asprintf(&join_strp, "%s%s%s", a_str,
++ (has_sep == 1) ? "" : "/", b_str);
++
++ if (retval < 0) {
++ goto out;
++ }
++
++ *strp_out = join_strp;
++
++ out:
++ return retval;
++}
++
++static int compare_strings(const void * a, const void * b)
++{
++ const char *a_str = *(char **)a;
++ const char *b_str = *(char **)b;
++
++ if (a_str == NULL && b_str == NULL) {
++ return 0;
++ }
++ else if (a_str == NULL) {
++ return -1;
++ }
++ else if (b_str == NULL) {
++ return 1;
++ }
++ else {
++ return strcmp(a_str, b_str);
++ }
++}
++
++static int filter_dirents(const struct dirent *d)
++{
++ return (d->d_type == DT_REG || d->d_type == DT_LNK);
++}
++
++static void try_to_display_directories_with_overrides(pam_handle_t *pamh,
++ char **motd_dir_path_split, int num_motd_dirs)
++{
++ struct dirent ***dirscans = NULL;
++ int *dirscans_sizes = NULL;
++ int dirscans_size_total = 0;
++ char **dirnames_all = NULL;
++ int i;
++ int i_dirnames = 0;
++
++ if (pamh == NULL || motd_dir_path_split == NULL) {
++ goto out;
++ }
++ if (num_motd_dirs < 1) {
++ goto out;
++ }
++
++ if ((dirscans = (struct dirent ***)calloc(num_motd_dirs,
++ sizeof(struct dirent **))) == NULL) {
++ pam_syslog(pamh, LOG_CRIT, "pam_motd: failed to allocate dirent arrays");
++ goto out;
++ }
++ if ((dirscans_sizes = (int *)calloc(num_motd_dirs, sizeof(int))) == NULL) {
++ pam_syslog(pamh, LOG_CRIT, "pam_motd: failed to allocate dirent array sizes");
++ goto out;
++ }
++
++ for (i = 0; i < num_motd_dirs; i++) {
++ dirscans_sizes[i] = scandir(motd_dir_path_split[i], &(dirscans[i]),
++ filter_dirents, alphasort);
++ if (dirscans_sizes[i] < 0) {
++ pam_syslog(pamh, LOG_ERR, "pam_motd: error scanning directory %s", motd_dir_path_split[i]);
++ dirscans_sizes[i] = 0;
++ }
++ dirscans_size_total += dirscans_sizes[i];
++ }
++
++ /* Allocate space for all file names found in the directories, including duplicates. */
++ if ((dirnames_all = (char **)calloc(dirscans_size_total,
++ sizeof(char *))) == NULL) {
++ pam_syslog(pamh, LOG_CRIT, "pam_motd: failed to allocate dirname array");
++ goto out;
++ }
++
++ for (i = 0; i < dirscans_size_total; i++) {
++ dirnames_all[i] = NULL;
++ }
++
++ for (i = 0; i < num_motd_dirs; i++) {
++ int j;
++
++ for (j = 0; j < dirscans_sizes[i]; j++) {
++ dirnames_all[i_dirnames] = dirscans[i][j]->d_name;
++ i_dirnames++;
++ }
++ }
++
++ qsort(dirnames_all, dirscans_size_total,
++ sizeof(const char *), compare_strings);
++
++ for (i = 0; i < dirscans_size_total; i++) {
++ int j;
++
++ if (dirnames_all[i] == NULL) {
++ continue;
++ }
++
++ /* Skip duplicate file names. */
++ if (i > 0 && strcmp(dirnames_all[i], dirnames_all[i - 1]) == 0) {
++ continue;
++ }
++
++ for (j = 0; j < num_motd_dirs; j++) {
++ char *abs_path = NULL;
++
++ if (join_dir_strings(&abs_path, motd_dir_path_split[j],
++ dirnames_all[i]) < 0) {
++ continue;
++ }
++
++ if (abs_path != NULL) {
++ int fd = open(abs_path, O_RDONLY, 0);
++ if (fd >= 0) {
++ try_to_display_fd(pamh, fd);
++ close(fd);
++
++ /* We displayed a file, skip to the next file name. */
++ break;
++ }
++ }
++ _pam_drop(abs_path);
++ }
++ }
++
++ out:
++ _pam_drop(dirnames_all);
++ for (i = 0; i < num_motd_dirs; i++) {
++ int j;
++ for (j = 0; j < dirscans_sizes[i]; j++) {
++ _pam_drop(dirscans[i][j]);
++ }
++ _pam_drop(dirscans[i]);
++ }
++ _pam_drop(dirscans_sizes);
++ _pam_drop(dirscans);
++
++ return;
++}
++
+ int pam_sm_open_session(pam_handle_t *pamh, int flags,
+ int argc, const char **argv)
+ {
+ int retval = PAM_IGNORE;
+ const char *motd_path = NULL;
++ char *motd_path_copy = NULL;
++ int num_motd_paths = 0;
++ char **motd_path_split = NULL;
+ const char *motd_dir_path = NULL;
++ char *motd_dir_path_copy = NULL;
++ int num_motd_dir_paths = 0;
++ char **motd_dir_path_split = NULL;
+
+ if (flags & PAM_SILENT) {
+ return retval;
+@@ -140,17 +363,47 @@ int pam_sm_open_session(pam_handle_t *pa
+ motd_dir_path = default_motd_dir;
+ }
+
+- if (motd_path != NULL) {
+- int fd = open(motd_path, O_RDONLY, 0);
++ motd_path_copy = strdup(motd_path);
++ if (motd_path_copy != NULL) {
++ if (pam_split_string(pamh, motd_path_copy, ':', &motd_path_split,
++ &num_motd_paths) == 0) {
++ goto out;
++ }
++ }
++
++ motd_dir_path_copy = strdup(motd_dir_path);
++ if (motd_dir_path_copy != NULL) {
++ if (pam_split_string(pamh, motd_dir_path_copy, ':',
++ &motd_dir_path_split, &num_motd_dir_paths) == 0) {
++ goto out;
++ }
++ }
++
++ if (motd_path_split != NULL) {
++ int i;
++
++ for (i = 0; i < num_motd_paths; i++) {
++ int fd = open(motd_path_split[i], O_RDONLY, 0);
+
+- if (fd >= 0) {
+- try_to_display_fd(pamh, fd);
+- close(fd);
++ if (fd >= 0) {
++ try_to_display_fd(pamh, fd);
++ close(fd);
++
++ /* We found and displayed a file, move onto next filename. */
++ break;
++ }
+ }
+ }
+
+- if (motd_dir_path != NULL)
+- try_to_display_directory(pamh, motd_dir_path);
++ if (motd_dir_path_split != NULL)
++ try_to_display_directories_with_overrides(pamh, motd_dir_path_split,
++ num_motd_dir_paths);
++
++ out:
++ _pam_drop(motd_path_copy);
++ _pam_drop(motd_path_split);
++ _pam_drop(motd_dir_path_copy);
++ _pam_drop(motd_dir_path_split);
+
+ return retval;
+ }
+diff -up Linux-PAM-1.3.1/xtests/Makefile.am.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/Makefile.am
+--- Linux-PAM-1.3.1/xtests/Makefile.am.pam_motd-support-multiple-motd-paths1 2017-02-10 11:10:15.000000000 +0100
++++ Linux-PAM-1.3.1/xtests/Makefile.am 2022-04-25 12:32:36.947663225 +0200
+@@ -32,7 +32,10 @@ EXTRA_DIST = run-xtests.sh tst-pam_dispa
+ tst-pam_substack5.pamd tst-pam_substack5a.pamd tst-pam_substack5.sh \
+ tst-pam_assemble_line1.pamd tst-pam_assemble_line1.sh \
+ tst-pam_pwhistory1.pamd tst-pam_pwhistory1.sh \
+- tst-pam_time1.pamd time.conf
++ tst-pam_time1.pamd time.conf \
++ tst-pam_motd.sh tst-pam_motd1.sh tst-pam_motd2.sh \
++ tst-pam_motd3.sh tst-pam_motd1.pamd \
++ tst-pam_motd2.pamd tst-pam_motd3.pamd
+
+ XTESTS = tst-pam_dispatch1 tst-pam_dispatch2 tst-pam_dispatch3 \
+ tst-pam_dispatch4 tst-pam_dispatch5 \
+@@ -41,7 +44,7 @@ XTESTS = tst-pam_dispatch1 tst-pam_dispa
+ tst-pam_access1 tst-pam_access2 tst-pam_access3 \
+ tst-pam_access4 tst-pam_limits1 tst-pam_succeed_if1 \
+ tst-pam_group1 tst-pam_authfail tst-pam_authsucceed \
+- tst-pam_pwhistory1 tst-pam_time1
++ tst-pam_pwhistory1 tst-pam_time1 tst-pam_motd
+
+ NOSRCTESTS = tst-pam_substack1 tst-pam_substack2 tst-pam_substack3 \
+ tst-pam_substack4 tst-pam_substack5 tst-pam_assemble_line1
+diff -up Linux-PAM-1.3.1/xtests/tst-pam_motd1.pamd.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/tst-pam_motd1.pamd
+--- Linux-PAM-1.3.1/xtests/tst-pam_motd1.pamd.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.947663225 +0200
++++ Linux-PAM-1.3.1/xtests/tst-pam_motd1.pamd 2022-04-25 12:32:36.947663225 +0200
+@@ -0,0 +1,3 @@
++#%PAM-1.0
++session required pam_permit.so
++session optional pam_motd.so motd=tst-pam_motd1.d/etc/motd motd_dir=tst-pam_motd1.d/etc/motd.d
+diff -up Linux-PAM-1.3.1/xtests/tst-pam_motd1.sh.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/tst-pam_motd1.sh
+--- Linux-PAM-1.3.1/xtests/tst-pam_motd1.sh.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.947663225 +0200
++++ Linux-PAM-1.3.1/xtests/tst-pam_motd1.sh 2022-04-25 12:32:36.947663225 +0200
+@@ -0,0 +1,36 @@
++#!/bin/bash
++
++TST_DIR="tst-pam_motd1.d"
++
++function tst_cleanup() {
++ rm -rf "${TST_DIR}"
++ rm -f tst-pam_motd1.out
++}
++
++mkdir -p ${TST_DIR}
++mkdir -p ${TST_DIR}/etc/motd.d
++
++# Verify the case of single motd and motd.d directory works
++echo "motd: /etc/motd" > ${TST_DIR}/etc/motd
++echo "motd: /etc/motd.d/test" > ${TST_DIR}/etc/motd.d/test
++
++./tst-pam_motd tst-pam_motd1 > tst-pam_motd1.out
++
++RET=$?
++
++motd_to_show_output=$(cat tst-pam_motd1.out | grep "motd: /etc/motd")
++if [ -z "${motd_to_show_output}" ];
++then
++ tst_cleanup
++ exit 1
++fi
++
++motd_dir_to_show_output=$(cat tst-pam_motd1.out | grep "motd: /etc/motd.d/test")
++if [ -z "${motd_dir_to_show_output}" ];
++then
++ tst_cleanup
++ exit 1
++fi
++
++tst_cleanup
++exit $RET
+diff -up Linux-PAM-1.3.1/xtests/tst-pam_motd2.pamd.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/tst-pam_motd2.pamd
+--- Linux-PAM-1.3.1/xtests/tst-pam_motd2.pamd.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.947663225 +0200
++++ Linux-PAM-1.3.1/xtests/tst-pam_motd2.pamd 2022-04-25 12:32:36.947663225 +0200
+@@ -0,0 +1,3 @@
++#%PAM-1.0
++session required pam_permit.so
++session optional pam_motd.so motd=tst-pam_motd2.d/etc/motd:tst-pam_motd2.d/run/motd:tst-pam_motd2.d/usr/lib/motd motd_dir=tst-pam_motd2.d/etc/motd.d:tst-pam_motd2.d/run/motd.d:tst-pam_motd2.d/usr/lib/motd.d
+diff -up Linux-PAM-1.3.1/xtests/tst-pam_motd2.sh.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/tst-pam_motd2.sh
+--- Linux-PAM-1.3.1/xtests/tst-pam_motd2.sh.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.947663225 +0200
++++ Linux-PAM-1.3.1/xtests/tst-pam_motd2.sh 2022-04-25 12:32:36.947663225 +0200
+@@ -0,0 +1,53 @@
++#!/bin/bash
++
++TST_DIR="tst-pam_motd2.d"
++
++function tst_cleanup() {
++ rm -rf "${TST_DIR}"
++ rm -f tst-pam_motd2.out
++}
++
++mkdir -p ${TST_DIR}
++mkdir -p ${TST_DIR}/etc/motd.d
++mkdir -p ${TST_DIR}/run/motd.d
++mkdir -p ${TST_DIR}/usr/lib/motd.d
++
++echo "motd: /etc/motd" > ${TST_DIR}/etc/motd
++echo "motd: /run/motd" > ${TST_DIR}/run/motd
++echo "motd: /usr/lib/motd" > ${TST_DIR}/usr/lib/motd
++
++# Drop a motd file in test directories such that every overriding
++# condition (for 3 directories in this case) will be seen.
++echo "motd: e0r0u1 in usr/lib - will show" > ${TST_DIR}/usr/lib/motd.d/e0r0u1.motd
++echo "motd: e0r1u0 in run - will show" > ${TST_DIR}/run/motd.d/e0r1u0.motd
++echo "motd: e0r1u1 in usr/lib - not show" > ${TST_DIR}/usr/lib/motd.d/e0r1u1.motd
++echo "motd: e0r1u1 in run - will show" > ${TST_DIR}/run/motd.d/e0r1u1.motd
++echo "motd: e1r0u0 in etc - will show" > ${TST_DIR}/etc/motd.d/e1r0u0.motd
++echo "motd: e1r0u1 in usr/lib - not show" > ${TST_DIR}/usr/lib/motd.d/e1r0u1.motd
++echo "motd: e1r0u1 in etc - will show" > ${TST_DIR}/etc/motd.d/e1r0u1.motd
++echo "motd: e1r1u0 in run - not show" > ${TST_DIR}/run/motd.d/e1r1u0.motd
++echo "motd: e1r1u0 in etc - will show" > ${TST_DIR}/etc/motd.d/e1r1u0.motd
++echo "motd: e1r1u1 in usr/lib - not show" > ${TST_DIR}/usr/lib/motd.d/e1r1u1.motd
++echo "motd: e1r1u1 in run - not show" > ${TST_DIR}/run/motd.d/e1r1u1.motd
++echo "motd: e1r1u1 in etc - will show" > ${TST_DIR}/etc/motd.d/e1r1u1.motd
++
++./tst-pam_motd tst-pam_motd2 > tst-pam_motd2.out
++
++RET=$?
++
++motd_to_show_output=$(cat tst-pam_motd2.out | grep "motd: /etc/motd")
++if [ -z "${motd_to_show_output}" ];
++then
++ tst_cleanup
++ exit 1
++fi
++
++motd_dir_not_show_output=$(cat tst-pam_motd2.out | grep "not show")
++if [ -n "${motd_dir_not_show_output}" ];
++then
++ tst_cleanup
++ exit 1
++fi
++
++tst_cleanup
++exit $RET
+diff -up Linux-PAM-1.3.1/xtests/tst-pam_motd3.pamd.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/tst-pam_motd3.pamd
+--- Linux-PAM-1.3.1/xtests/tst-pam_motd3.pamd.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.947663225 +0200
++++ Linux-PAM-1.3.1/xtests/tst-pam_motd3.pamd 2022-04-25 12:32:36.947663225 +0200
+@@ -0,0 +1,3 @@
++#%PAM-1.0
++session required pam_permit.so
++session optional pam_motd.so motd=tst-pam_motd3.d/etc/motd:tst-pam_motd3.d/run/motd:tst-pam_motd3.d/usr/lib/motd motd_dir=tst-pam_motd3.d/etc/motd.d:tst-pam_motd3.d/run/motd.d:tst-pam_motd3.d/usr/lib/motd.d
+diff -up Linux-PAM-1.3.1/xtests/tst-pam_motd3.sh.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/tst-pam_motd3.sh
+--- Linux-PAM-1.3.1/xtests/tst-pam_motd3.sh.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.947663225 +0200
++++ Linux-PAM-1.3.1/xtests/tst-pam_motd3.sh 2022-04-25 12:32:36.947663225 +0200
+@@ -0,0 +1,53 @@
++#!/bin/bash
++
++TST_DIR="tst-pam_motd3.d"
++
++function tst_cleanup() {
++ rm -rf "${TST_DIR}"
++ rm -f tst-pam_motd3.out
++}
++
++mkdir -p ${TST_DIR}
++mkdir -p ${TST_DIR}/etc/motd.d
++mkdir -p ${TST_DIR}/run/motd.d
++mkdir -p ${TST_DIR}/usr/lib/motd.d
++
++# Verify motd is still displayed when not overridden
++echo "motd: test-show in run - show" > ${TST_DIR}/run/motd.d/test-show.motd
++
++# Test overridden by a symlink to a file that isn't /dev/null; symlink target should show
++echo "motd: hidden-by-symlink in usr/lib - not show" > ${TST_DIR}/usr/lib/motd.d/hidden-by-symlink.motd
++echo "motd: test-from-symlink - show" > ${TST_DIR}/test-from-symlink.motd
++ln -sr ${TST_DIR}/test-from-symlink.motd ${TST_DIR}/run/motd.d/hidden-by-symlink.motd
++
++# Test hidden by a null symlink
++echo "motd: hidden-by-null-symlink in run - not show" > ${TST_DIR}/run/motd.d/hidden-by-null-symlink.motd
++ln -s /dev/null ${TST_DIR}/etc/motd.d/hidden-by-null-symlink.motd
++
++./tst-pam_motd tst-pam_motd3 > tst-pam_motd3.out
++
++RET=$?
++
++motd_dir_not_show_output=$(cat tst-pam_motd3.out | grep "not show")
++if [ -n "${motd_dir_not_show_output}" ];
++then
++ tst_cleanup
++ exit 1
++fi
++
++motd_test_show_output=$(cat tst-pam_motd3.out | grep "test-show.*- show")
++if [ -z "${motd_test_show_output}" ];
++then
++ tst_cleanup
++ exit 1
++fi
++
++motd_general_symlink_show_output=$(cat tst-pam_motd3.out | grep "test-from-symlink.*- show")
++if [ -z "${motd_general_symlink_show_output}" ];
++then
++ tst_cleanup
++ exit 1
++fi
++
++tst_cleanup
++exit $RET
+diff -up Linux-PAM-1.3.1/xtests/tst-pam_motd.c.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/tst-pam_motd.c
+--- Linux-PAM-1.3.1/xtests/tst-pam_motd.c.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.947663225 +0200
++++ Linux-PAM-1.3.1/xtests/tst-pam_motd.c 2022-04-25 12:32:36.947663225 +0200
+@@ -0,0 +1,69 @@
++/*
++ * 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.
++ */
++
++#ifdef HAVE_CONFIG_H
++#include
++#endif
++
++#include
++#include
++#include
++#include
++
++static struct pam_conv conv = {
++ misc_conv,
++ NULL
++};
++
++int main(int argc, char *argv[])
++{
++ pam_handle_t *pamh=NULL;
++ char *tst_arg = NULL;
++ int retval;
++
++ if (argc > 1)
++ tst_arg = argv[1];
++
++ retval = pam_start(tst_arg, NULL, &conv, &pamh);
++
++ retval = pam_open_session(pamh, 0);
++
++ retval = pam_close_session(pamh, 0);
++
++ if (pam_end(pamh,retval) != PAM_SUCCESS) { /* close Linux-PAM */
++ pamh = NULL;
++ exit(1);
++ }
++
++ return ( retval == PAM_SUCCESS ? 0:1 ); /* indicate success */
++}
+diff -up Linux-PAM-1.3.1/xtests/tst-pam_motd.sh.pam_motd-support-multiple-motd-paths1 Linux-PAM-1.3.1/xtests/tst-pam_motd.sh
+--- Linux-PAM-1.3.1/xtests/tst-pam_motd.sh.pam_motd-support-multiple-motd-paths1 2022-04-25 12:32:36.947663225 +0200
++++ Linux-PAM-1.3.1/xtests/tst-pam_motd.sh 2022-04-25 12:32:36.947663225 +0200
+@@ -0,0 +1,7 @@
++#!/bin/bash
++
++set -e
++
++./tst-pam_motd1.sh
++./tst-pam_motd2.sh
++./tst-pam_motd3.sh
diff --git a/SOURCES/pam-1.3.1-pam-usertype-SYS_UID_MAX.patch b/SOURCES/pam-1.3.1-pam-usertype-SYS_UID_MAX.patch
new file mode 100644
index 0000000..4881a5e
--- /dev/null
+++ b/SOURCES/pam-1.3.1-pam-usertype-SYS_UID_MAX.patch
@@ -0,0 +1,75 @@
+diff -up Linux-PAM-1.3.1/configure.ac.pam-usertype-SYS_UID_MAX Linux-PAM-1.3.1/configure.ac
+--- Linux-PAM-1.3.1/configure.ac.pam-usertype-SYS_UID_MAX 2022-06-22 16:41:09.169146826 +0200
++++ Linux-PAM-1.3.1/configure.ac 2022-06-22 16:43:54.343373619 +0200
+@@ -615,12 +615,6 @@ if test x"$opt_uidmin" == x; then
+ fi
+ AC_DEFINE_UNQUOTED(PAM_USERTYPE_UIDMIN, $opt_uidmin, [Minimum regular user uid.])
+
+-AC_ARG_WITH([sysuidmin], AS_HELP_STRING([--with-sysuidmin=],[default value for system user min uid (101)]), opt_sysuidmin=$withval)
+-if test x"$opt_sysuidmin" == x; then
+- opt_sysuidmin=101
+-fi
+-AC_DEFINE_UNQUOTED(PAM_USERTYPE_SYSUIDMIN, $opt_sysuidmin, [Minimum system user uid.])
+-
+ AC_ARG_WITH([kerneloverflowuid], AS_HELP_STRING([--with-kernel-overflow-uid=],[kernel overflow uid, default (uint16_t)-2=65534]), opt_kerneloverflowuid=$withval)
+ if test x"$opt_kerneloverflowuid" == x; then
+ opt_kerneloverflowuid=65534
+diff -up Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.8.xml.pam-usertype-SYS_UID_MAX Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.8.xml
+--- Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.8.xml.pam-usertype-SYS_UID_MAX 2022-06-22 16:41:09.155146722 +0200
++++ Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.8.xml 2022-06-22 16:41:09.169146826 +0200
+@@ -31,7 +31,7 @@
+ pam_usertype.so is designed to succeed or fail authentication
+ based on type of the account of the authenticated user.
+ The type of the account is decided with help of
+- SYS_UID_MIN and SYS_UID_MAX
++ SYS_UID_MAX
+ settings in /etc/login.defs. One use is to select
+ whether to load other modules based on this test.
+
+diff -up Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.c.pam-usertype-SYS_UID_MAX Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.c
+--- Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.c.pam-usertype-SYS_UID_MAX 2022-06-22 16:41:09.155146722 +0200
++++ Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.c 2022-06-22 16:41:09.169146826 +0200
+@@ -277,7 +277,6 @@ static int
+ pam_usertype_is_system(pam_handle_t *pamh, uid_t uid)
+ {
+ uid_t uid_min;
+- uid_t sys_min;
+ uid_t sys_max;
+
+ if (uid == (uid_t)-1) {
+@@ -285,21 +284,19 @@ pam_usertype_is_system(pam_handle_t *pam
+ return PAM_USER_UNKNOWN;
+ }
+
+- if (uid <= 99) {
+- /* Reserved. */
+- return PAM_SUCCESS;
+- }
+-
+ if (uid == PAM_USERTYPE_OVERFLOW_UID) {
+ /* nobody */
+ return PAM_SUCCESS;
+ }
+
+ uid_min = pam_usertype_get_id(pamh, "UID_MIN", PAM_USERTYPE_UIDMIN);
+- sys_min = pam_usertype_get_id(pamh, "SYS_UID_MIN", PAM_USERTYPE_SYSUIDMIN);
+ sys_max = pam_usertype_get_id(pamh, "SYS_UID_MAX", uid_min - 1);
+
+- return uid >= sys_min && uid <= sys_max ? PAM_SUCCESS : PAM_AUTH_ERR;
++ if (uid <= sys_max && uid < uid_min) {
++ return PAM_SUCCESS;
++ }
++
++ return PAM_AUTH_ERR;
+ }
+
+ static int
+@@ -336,7 +333,7 @@ pam_usertype_evaluate(struct pam_usertyp
+
+ /**
+ * Arguments:
+- * - issystem: uid in
++ * - issystem: uid less than SYS_UID_MAX
+ * - isregular: not issystem
+ * - use_uid: use user that runs application not that is being authenticate (same as in pam_succeed_if)
+ * - audit: log unknown users to syslog
diff --git a/SOURCES/pamtmp.conf b/SOURCES/pamtmp.conf
index e94a860..e4cfe2a 100644
--- a/SOURCES/pamtmp.conf
+++ b/SOURCES/pamtmp.conf
@@ -1,3 +1,4 @@
d /run/console 0755 root root -
d /run/faillock 0755 root root -
d /run/sepermit 0755 root root -
+d /run/motd.d 0755 root root -
diff --git a/SPECS/pam.spec b/SPECS/pam.spec
index 56d1104..8b0ed82 100644
--- a/SPECS/pam.spec
+++ b/SPECS/pam.spec
@@ -3,7 +3,7 @@
Summary: An extensible library which provides authentication for applications
Name: pam
Version: 1.3.1
-Release: 16%{?dist}
+Release: 22%{?dist}
# The library is BSD licensed with option to relicense as GPLv2+
# - this option is redundant as the BSD license allows that anyway.
# pam_timestamp, pam_loginuid, and pam_console modules are GPLv2+.
@@ -69,6 +69,22 @@ Patch49: pam-1.3.1-namespace-gdm-doc.patch
Patch50: pam-1.3.1-pam-userdb-prevent-garbage-characters-from-db.patch
# https://github.com/linux-pam/linux-pam/commit/3234488f2c52a021eec87df1990d256314c21bff
Patch51: pam-1.3.1-pam-limits-unlimited-value.patch
+# https://github.com/linux-pam/linux-pam/commit/a35e092e24ee7632346a0e1b4a203c04d4cd2c62
+Patch52: pam-1.3.1-pam-keyinit-thread-safe.patch
+# https://github.com/linux-pam/linux-pam/commit/f9c9c72121eada731e010ab3620762bcf63db08f
+Patch53: pam-1.3.1-pam-motd-support-multiple-motd-paths.patch
+# https://github.com/linux-pam/linux-pam/commit/8eaf5570cf011148a0b55c53570df5edaafebdb0
+Patch54: pam-1.3.1-pam-motd-fix-segmentation-fault.patch
+# https://github.com/linux-pam/linux-pam/commit/62cd745d730e5ba13d5d7092ac566fc0b2148e61
+Patch55: pam-1.3.1-pam-motd-fix-memory-leak.patch
+# Needed by the next patch. Already upstreamed
+Patch56: pam-1.3.1-pam-cc-compat.patch
+Patch57: pam-1.3.1-inline.patch
+# https://github.com/linux-pam/linux-pam/commit/9bcbe96d9e82a23d983c0618178a8dc25596ac2d
+# https://github.com/linux-pam/linux-pam/commit/fc867a9e22eac2c9a0ed0577776bba4df21c9aad
+Patch58: pam-1.3.1-faillock-load-conf-from-file.patch
+# https://github.com/linux-pam/linux-pam/commit/370064ef6f99581b08d473a42bb3417d5dda3e4e
+Patch59: pam-1.3.1-pam-usertype-SYS_UID_MAX.patch
%define _pamlibdir %{_libdir}
%define _moduledir %{_libdir}/security
@@ -168,6 +184,14 @@ cp %{SOURCE18} .
%patch49 -p1 -b .namespace-gdm-doc
%patch50 -p1 -b .pam-userdb-prevent-garbage-characters-from-db
%patch51 -p1 -b .pam-limits-unlimited-value
+%patch52 -p1 -b .pam-keyinit-thread-safe
+%patch53 -p1 -b .pam-motd-support-multiple-motd-paths
+%patch54 -p1 -b .pam-motd-fix-segmentation-fault
+%patch55 -p1 -b .pam-motd-fix-memory-leak
+%patch56 -p1 -b .pam-cc-compat
+%patch57 -p1 -b .inline
+%patch58 -p1 -b .faillock-load-conf-from-file
+%patch59 -p1 -b .pam-usertype-SYS_UID_MAX
autoreconf -i
@@ -222,6 +246,9 @@ install -m 644 %{SOURCE16} $RPM_BUILD_ROOT%{_pamconfdir}/postlogin
install -m 600 /dev/null $RPM_BUILD_ROOT%{_secconfdir}/opasswd
install -d -m 755 $RPM_BUILD_ROOT/var/log
install -d -m 755 $RPM_BUILD_ROOT/var/run/faillock
+install -d -m 755 $RPM_BUILD_ROOT%{_sysconfdir}/motd.d
+install -d -m 755 $RPM_BUILD_ROOT/usr/lib/motd.d
+install -d -m 755 $RPM_BUILD_ROOT/run/motd.d
# Install man pages.
install -m 644 %{SOURCE12} %{SOURCE13} %{SOURCE17} $RPM_BUILD_ROOT%{_mandir}/man5/
@@ -399,6 +426,9 @@ done
%dir /var/run/sepermit
%endif
%dir /var/run/faillock
+%dir %{_sysconfdir}/motd.d
+%dir /run/motd.d
+%dir /usr/lib/motd.d
%{_prefix}/lib/tmpfiles.d/pam.conf
%{_mandir}/man5/*
%{_mandir}/man8/*
@@ -414,6 +444,26 @@ done
%doc doc/specs/rfc86.0.txt
%changelog
+* Wed Jul 13 2022 Iker Pedrosa - 1.3.1-22
+- Regenerate the /run/motd.d at each boot. Resolves: #2104878
+
+* Thu Jun 23 2022 Iker Pedrosa - 1.3.1-21
+- pam_usertype: only use SYS_UID_MAX for system users. Resolves: #1949137
+
+* Thu May 26 2022 Iker Pedrosa - 1.3.1-20
+- faillock: load configuration from file. Resolves: #1978029
+
+* Mon May 23 2022 Iker Pedrosa - 1.3.1-19
+- Add the motd.d directories (empty) to silence warnings and to
+ provide proper ownership for them. Resolves: #2014458
+
+* Thu May 19 2022 Iker Pedrosa - 1.3.1-18
+- pam_motd: fix memory leak. Resolves: #2014458
+
+* Tue May 17 2022 Iker Pedrosa - 1.3.1-17
+- pam_keyinit: thread-safe implementation. Resolves: #1997969
+- pam_motd: support multiple motd paths specified, with filename overrides. Resolves: #2014458
+
* Fri Jan 28 2022 Iker Pedrosa - 1.3.1-16
- pam_limits: "Unlimited" is not a valid value for RLIMIT_NOFILE. Resolves: #2047655