From bdc917377c0cd46f2879ce5190505ba28d5ffa1d Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 27 Sep 2022 16:43:10 -0400 Subject: [PATCH] import pam-1.3.1-22.el8 --- ...m-1.3.1-faillock-load-conf-from-file.patch | 1212 +++++++++++++++++ SOURCES/pam-1.3.1-inline.patch | 105 ++ SOURCES/pam-1.3.1-pam-cc-compat.patch | 63 + .../pam-1.3.1-pam-keyinit-thread-safe.patch | 125 ++ .../pam-1.3.1-pam-motd-fix-memory-leak.patch | 57 + ....3.1-pam-motd-fix-segmentation-fault.patch | 133 ++ ...pam-motd-support-multiple-motd-paths.patch | 691 ++++++++++ .../pam-1.3.1-pam-usertype-SYS_UID_MAX.patch | 75 + SOURCES/pamtmp.conf | 1 + SPECS/pam.spec | 52 +- 10 files changed, 2513 insertions(+), 1 deletion(-) create mode 100644 SOURCES/pam-1.3.1-faillock-load-conf-from-file.patch create mode 100644 SOURCES/pam-1.3.1-inline.patch create mode 100644 SOURCES/pam-1.3.1-pam-cc-compat.patch create mode 100644 SOURCES/pam-1.3.1-pam-keyinit-thread-safe.patch create mode 100644 SOURCES/pam-1.3.1-pam-motd-fix-memory-leak.patch create mode 100644 SOURCES/pam-1.3.1-pam-motd-fix-segmentation-fault.patch create mode 100644 SOURCES/pam-1.3.1-pam-motd-support-multiple-motd-paths.patch create mode 100644 SOURCES/pam-1.3.1-pam-usertype-SYS_UID_MAX.patch 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