Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
ipedrosa | a4a0ff7a56 | |
Allison Karlitskaya | 721d8b383e | |
ipedrosa | 55e417db2c | |
ipedrosa | 456eb7078d | |
ipedrosa | c757a5f3a5 | |
ipedrosa | b3441aa346 | |
ipedrosa | c99677c8e8 | |
ikerexxe | b173f42452 | |
Pavel Březina | a2d2a3801b |
|
@ -0,0 +1,126 @@
|
|||
From 0fa5f9d4184928c28689b673fb06bb8b4d88a0c2 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
|
||||
Date: Thu, 6 Feb 2020 12:41:15 +0100
|
||||
Subject: [PATCH] pam_usertype: remove dependency on pam_modutil_search_key
|
||||
|
||||
This is needed to correctly backport the patch to this version.
|
||||
---
|
||||
modules/pam_usertype/pam_usertype.c | 88 ++++++++++++++++++++++++++++-
|
||||
1 file changed, 87 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/modules/pam_usertype/pam_usertype.c b/modules/pam_usertype/pam_usertype.c
|
||||
index d3629c137d98545871d24ff26c06d8377068141f..741956b05809d8d6247fe2eba82ae14427cfeae4 100644
|
||||
--- a/modules/pam_usertype/pam_usertype.c
|
||||
+++ b/modules/pam_usertype/pam_usertype.c
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
+#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
@@ -72,6 +73,91 @@ struct pam_usertype_opts {
|
||||
int audit;
|
||||
};
|
||||
|
||||
+/* taken from pam_umask.c and reformatted */
|
||||
+static char *
|
||||
+search_key (const char *filename,
|
||||
+ const char *key)
|
||||
+{
|
||||
+ FILE *fp;
|
||||
+ char *buf = NULL;
|
||||
+ size_t buflen = 0;
|
||||
+ char *retval = NULL;
|
||||
+
|
||||
+ fp = fopen (filename, "r");
|
||||
+ if (NULL == fp) {
|
||||
+ return NULL;
|
||||
+ }
|
||||
+
|
||||
+ while (!feof (fp)) {
|
||||
+ char *tmp, *cp;
|
||||
+#if defined(HAVE_GETLINE)
|
||||
+ ssize_t n = getline (&buf, &buflen, fp);
|
||||
+#elif defined (HAVE_GETDELIM)
|
||||
+ ssize_t n = getdelim (&buf, &buflen, '\n', fp);
|
||||
+#else
|
||||
+ ssize_t n;
|
||||
+
|
||||
+ if (buf == NULL) {
|
||||
+ buflen = BUF_SIZE;
|
||||
+ buf = malloc (buflen);
|
||||
+ if (buf == NULL) {
|
||||
+ fclose (fp);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ buf[0] = '\0';
|
||||
+ if (fgets (buf, buflen - 1, fp) == NULL) {
|
||||
+ break;
|
||||
+ } else if (buf != NULL) {
|
||||
+ n = strlen (buf);
|
||||
+ } else {
|
||||
+ n = 0;
|
||||
+ }
|
||||
+#endif /* HAVE_GETLINE / HAVE_GETDELIM */
|
||||
+
|
||||
+ cp = buf;
|
||||
+
|
||||
+ if (n < 1) {
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ tmp = strchr (cp, '#'); /* remove comments */
|
||||
+ if (tmp) {
|
||||
+ *tmp = '\0';
|
||||
+ }
|
||||
+
|
||||
+ while (isspace ((int)*cp)) { /* remove spaces and tabs */
|
||||
+ ++cp;
|
||||
+ }
|
||||
+
|
||||
+ if (*cp == '\0') { /* ignore empty lines */
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (cp[strlen (cp) - 1] == '\n') {
|
||||
+ cp[strlen (cp) - 1] = '\0';
|
||||
+ }
|
||||
+
|
||||
+ tmp = strsep (&cp, " \t=");
|
||||
+ if (cp != NULL) {
|
||||
+ while (isspace ((int)*cp) || *cp == '=') {
|
||||
+ ++cp;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (strcasecmp (tmp, key) == 0) {
|
||||
+ retval = strdup (cp);
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ fclose (fp);
|
||||
+ free (buf);
|
||||
+
|
||||
+ return retval;
|
||||
+}
|
||||
+
|
||||
static int
|
||||
pam_usertype_parse_args(struct pam_usertype_opts *opts,
|
||||
pam_handle_t *pamh,
|
||||
@@ -170,7 +256,7 @@ pam_usertype_get_id(pam_handle_t *pamh,
|
||||
char *ep;
|
||||
uid_t uid;
|
||||
|
||||
- value = pam_modutil_search_key(pamh, LOGIN_DEFS, key);
|
||||
+ value = search_key(LOGIN_DEFS, key);
|
||||
if (value == NULL) {
|
||||
return default_value;
|
||||
}
|
||||
--
|
||||
2.24.1
|
||||
|
|
@ -0,0 +1,684 @@
|
|||
From 88df4b5383b776b7b8ee9eb4c33231d54185b1e2 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
|
||||
Date: Fri, 10 Jan 2020 15:53:35 +0100
|
||||
Subject: [PATCH] pam_usertype: new module to tell if uid is in login.defs
|
||||
ranges
|
||||
|
||||
This module will check if the user account type is system or regular based
|
||||
on its uid. To evaluate the condition it will use 0-99 reserved range
|
||||
together with `SYS_UID_MIN` and `SYS_UID_MAX` values from `/etc/login.defs`.
|
||||
|
||||
If these values are not set, it uses configure-time defaults
|
||||
`--with-sys-uid-min` and `--with-uid-min` (according to `login.defs` man page
|
||||
`SYS_UID_MAX` defaults to `UID_MIN - 1`.
|
||||
|
||||
This information can be used to skip specific module in pam stack
|
||||
based on the account type. `pam_succeed_if uid < 1000` is used at the moment
|
||||
however it does not reflect changes to `login.defs`.
|
||||
---
|
||||
configure.ac | 22 ++
|
||||
modules/Makefile.am | 2 +-
|
||||
modules/pam_usertype/Makefile.am | 34 +++
|
||||
modules/pam_usertype/README.xml | 41 +++
|
||||
modules/pam_usertype/pam_usertype.8.xml | 170 +++++++++++++
|
||||
modules/pam_usertype/pam_usertype.c | 319 ++++++++++++++++++++++++
|
||||
modules/pam_usertype/tst-pam_usertype | 2 +
|
||||
7 files changed, 589 insertions(+), 1 deletion(-)
|
||||
create mode 100644 modules/pam_usertype/Makefile.am
|
||||
create mode 100644 modules/pam_usertype/README.xml
|
||||
create mode 100644 modules/pam_usertype/pam_usertype.8.xml
|
||||
create mode 100644 modules/pam_usertype/pam_usertype.c
|
||||
create mode 100755 modules/pam_usertype/tst-pam_usertype
|
||||
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index 0267202d2f56cbb641ce74d283bc4ba2a4b3d0d9..f10a09e14c10639b91c356d6ef883da4a0a87a66 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -606,6 +606,27 @@ AC_SUBST([HAVE_KEY_MANAGEMENT], $HAVE_KEY_MANAGEMENT)
|
||||
|
||||
AM_CONDITIONAL([HAVE_KEY_MANAGEMENT], [test "$have_key_syscalls" = 1])
|
||||
|
||||
+dnl
|
||||
+dnl Get values for default uid ranges in login.defs used in pam_usertype
|
||||
+dnl
|
||||
+AC_ARG_WITH([uidmin], AS_HELP_STRING([--with-uidmin=<number>],[default value for regular user min uid (1000)]), opt_uidmin=$withval)
|
||||
+if test x"$opt_uidmin" == x; then
|
||||
+ opt_uidmin=1000
|
||||
+fi
|
||||
+AC_DEFINE_UNQUOTED(PAM_USERTYPE_UIDMIN, $opt_uidmin, [Minimum regular user uid.])
|
||||
+
|
||||
+AC_ARG_WITH([sysuidmin], AS_HELP_STRING([--with-sysuidmin=<number>],[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=<number>],[kernel overflow uid, default (uint16_t)-2=65534]), opt_kerneloverflowuid=$withval)
|
||||
+if test x"$opt_kerneloverflowuid" == x; then
|
||||
+ opt_kerneloverflowuid=65534
|
||||
+fi
|
||||
+AC_DEFINE_UNQUOTED(PAM_USERTYPE_OVERFLOW_UID, $opt_kerneloverflowuid, [Kernel overflow uid.])
|
||||
+
|
||||
dnl Files to be created from when we run configure
|
||||
AC_CONFIG_FILES([Makefile libpam/Makefile libpamc/Makefile libpamc/test/Makefile \
|
||||
libpam_misc/Makefile conf/Makefile conf/pam_conv1/Makefile \
|
||||
@@ -636,6 +657,7 @@ AC_CONFIG_FILES([Makefile libpam/Makefile libpamc/Makefile libpamc/test/Makefile
|
||||
modules/pam_timestamp/Makefile modules/pam_tty_audit/Makefile \
|
||||
modules/pam_umask/Makefile \
|
||||
modules/pam_unix/Makefile modules/pam_userdb/Makefile \
|
||||
+ modules/pam_usertype/Makefile \
|
||||
modules/pam_warn/Makefile modules/pam_wheel/Makefile \
|
||||
modules/pam_xauth/Makefile doc/Makefile doc/specs/Makefile \
|
||||
doc/man/Makefile doc/sag/Makefile doc/adg/Makefile \
|
||||
diff --git a/modules/Makefile.am b/modules/Makefile.am
|
||||
index 5149181e2d1aeefbab8876433e8a54848ec56fc6..c1b5c5611c79e666bbd2f94fa0712a2b78bd2f5f 100644
|
||||
--- a/modules/Makefile.am
|
||||
+++ b/modules/Makefile.am
|
||||
@@ -12,7 +12,7 @@ SUBDIRS = pam_access pam_cracklib pam_debug pam_deny pam_echo \
|
||||
pam_selinux pam_sepermit pam_shells pam_stress \
|
||||
pam_succeed_if pam_time pam_timestamp \
|
||||
pam_tty_audit pam_umask \
|
||||
- pam_unix pam_userdb pam_warn pam_wheel pam_xauth
|
||||
+ pam_unix pam_userdb pam_usertype pam_warn pam_wheel pam_xauth
|
||||
|
||||
CLEANFILES = *~
|
||||
|
||||
diff --git a/modules/pam_usertype/Makefile.am b/modules/pam_usertype/Makefile.am
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..1646bc34f2fbc44032af5a5b38d160614b247b72
|
||||
--- /dev/null
|
||||
+++ b/modules/pam_usertype/Makefile.am
|
||||
@@ -0,0 +1,34 @@
|
||||
+#
|
||||
+# Copyright (c) 2005, 2006, 2009 Thorsten Kukuk <kukuk@suse.de>
|
||||
+# Copyright (c) 2020 Red Hat, Inc.
|
||||
+#
|
||||
+
|
||||
+CLEANFILES = *~
|
||||
+MAINTAINERCLEANFILES = $(MANS) README
|
||||
+
|
||||
+EXTRA_DIST = README ${MANS} ${XMLS} tst-pam_usertype
|
||||
+
|
||||
+TESTS = tst-pam_usertype
|
||||
+
|
||||
+man_MANS = pam_usertype.8
|
||||
+
|
||||
+XMLS = README.xml pam_usertype.8.xml
|
||||
+
|
||||
+securelibdir = $(SECUREDIR)
|
||||
+secureconfdir = $(SCONFIGDIR)
|
||||
+
|
||||
+AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include \
|
||||
+ $(WARN_CFLAGS)
|
||||
+AM_LDFLAGS = -no-undefined -avoid-version -module
|
||||
+if HAVE_VERSIONING
|
||||
+ AM_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map
|
||||
+endif
|
||||
+
|
||||
+securelib_LTLIBRARIES = pam_usertype.la
|
||||
+pam_usertype_la_LIBADD = $(top_builddir)/libpam/libpam.la
|
||||
+
|
||||
+if ENABLE_REGENERATE_MAN
|
||||
+noinst_DATA = README
|
||||
+README: pam_usertype.8.xml
|
||||
+-include $(top_srcdir)/Make.xml.rules
|
||||
+endif
|
||||
diff --git a/modules/pam_usertype/README.xml b/modules/pam_usertype/README.xml
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..58550465459222ace5e346c32b54cf6776eeeec5
|
||||
--- /dev/null
|
||||
+++ b/modules/pam_usertype/README.xml
|
||||
@@ -0,0 +1,41 @@
|
||||
+<?xml version="1.0" encoding='UTF-8'?>
|
||||
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
|
||||
+"http://www.docbook.org/xml/4.3/docbookx.dtd"
|
||||
+[
|
||||
+<!--
|
||||
+<!ENTITY pamaccess SYSTEM "pam_usertype.8.xml">
|
||||
+-->
|
||||
+]>
|
||||
+
|
||||
+<article>
|
||||
+
|
||||
+ <articleinfo>
|
||||
+
|
||||
+ <title>
|
||||
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
+ href="pam_usertype.8.xml" xpointer='xpointer(//refnamediv[@id = "pam_usertype-name"]/*)'/>
|
||||
+ </title>
|
||||
+
|
||||
+ </articleinfo>
|
||||
+
|
||||
+ <section>
|
||||
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
+ href="pam_usertype.8.xml" xpointer='xpointer(//refsect1[@id = "pam_usertype-description"]/*)'/>
|
||||
+ </section>
|
||||
+
|
||||
+ <section>
|
||||
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
+ href="pam_usertype.8.xml" xpointer='xpointer(//refsect1[@id = "pam_usertype-options"]/*)'/>
|
||||
+ </section>
|
||||
+
|
||||
+ <section>
|
||||
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
+ href="pam_usertype.8.xml" xpointer='xpointer(//refsect1[@id = "pam_usertype-examples"]/*)'/>
|
||||
+ </section>
|
||||
+
|
||||
+ <section>
|
||||
+ <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||
+ href="pam_usertype.8.xml" xpointer='xpointer(//refsect1[@id = "pam_usertype-author"]/*)'/>
|
||||
+ </section>
|
||||
+
|
||||
+</article>
|
||||
diff --git a/modules/pam_usertype/pam_usertype.8.xml b/modules/pam_usertype/pam_usertype.8.xml
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..1ba4ee71dcd4faee1bf5293c718d1bdf823689f0
|
||||
--- /dev/null
|
||||
+++ b/modules/pam_usertype/pam_usertype.8.xml
|
||||
@@ -0,0 +1,170 @@
|
||||
+<?xml version="1.0" encoding='UTF-8'?>
|
||||
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
|
||||
+ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
|
||||
+
|
||||
+
|
||||
+<refentry id='pam_usertype'>
|
||||
+ <refmeta>
|
||||
+ <refentrytitle>pam_usertype</refentrytitle>
|
||||
+ <manvolnum>8</manvolnum>
|
||||
+ <refmiscinfo class='sectdesc'>Linux-PAM</refmiscinfo>
|
||||
+ </refmeta>
|
||||
+
|
||||
+ <refnamediv id='pam_usertype-name'>
|
||||
+ <refname>pam_usertype</refname>
|
||||
+ <refpurpose>check if the authenticated user is a system or regular account</refpurpose>
|
||||
+ </refnamediv>
|
||||
+
|
||||
+
|
||||
+ <refsynopsisdiv>
|
||||
+ <cmdsynopsis id='pam_usertype-cmdsynopsis'>
|
||||
+ <command>pam_usertype.so</command>
|
||||
+ <arg choice='opt' rep='repeat'><replaceable>flag</replaceable></arg>
|
||||
+ <arg choice='req'><replaceable>condition</replaceable></arg>
|
||||
+ </cmdsynopsis>
|
||||
+ </refsynopsisdiv>
|
||||
+
|
||||
+
|
||||
+ <refsect1 id='pam_usertype-description'>
|
||||
+ <title>DESCRIPTION</title>
|
||||
+ <para>
|
||||
+ 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
|
||||
+ <emphasis>SYS_UID_MIN</emphasis> and <emphasis>SYS_UID_MAX</emphasis>
|
||||
+ settings in <emphasis>/etc/login.defs</emphasis>. One use is to select
|
||||
+ whether to load other modules based on this test.
|
||||
+ </para>
|
||||
+
|
||||
+ <para>
|
||||
+ The module should be given only one condition as module argument.
|
||||
+ Authentication will succeed only if the condition is met.
|
||||
+ </para>
|
||||
+ </refsect1>
|
||||
+
|
||||
+ <refsect1 id="pam_usertype-options">
|
||||
+ <title>OPTIONS</title>
|
||||
+ <para>
|
||||
+ The following <emphasis>flag</emphasis>s are supported:
|
||||
+ </para>
|
||||
+
|
||||
+ <variablelist>
|
||||
+ <varlistentry>
|
||||
+ <term><option>use_uid</option></term>
|
||||
+ <listitem>
|
||||
+ <para>
|
||||
+ Evaluate conditions using the account of the user whose UID
|
||||
+ the application is running under instead of the user being
|
||||
+ authenticated.
|
||||
+ </para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+ <varlistentry>
|
||||
+ <term><option>audit</option></term>
|
||||
+ <listitem>
|
||||
+ <para>
|
||||
+ Log unknown users to the system log.
|
||||
+ </para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+ </variablelist>
|
||||
+
|
||||
+ <para>
|
||||
+ Available <emphasis>condition</emphasis>s are:
|
||||
+ </para>
|
||||
+
|
||||
+ <variablelist>
|
||||
+ <varlistentry>
|
||||
+ <term><option>issystem</option></term>
|
||||
+ <listitem>
|
||||
+ <para>Succeed if the user is a system user.</para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+ <varlistentry>
|
||||
+ <term><option>isregular</option></term>
|
||||
+ <listitem>
|
||||
+ <para>Succeed if the user is a regular user.</para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+ </variablelist>
|
||||
+ </refsect1>
|
||||
+
|
||||
+ <refsect1 id="pam_usertype-types">
|
||||
+ <title>MODULE TYPES PROVIDED</title>
|
||||
+ <para>
|
||||
+ All module types (<option>account</option>, <option>auth</option>,
|
||||
+ <option>password</option> and <option>session</option>) are provided.
|
||||
+ </para>
|
||||
+ </refsect1>
|
||||
+
|
||||
+ <refsect1 id='pam_usertype-return_values'>
|
||||
+ <title>RETURN VALUES</title>
|
||||
+ <variablelist>
|
||||
+
|
||||
+ <varlistentry>
|
||||
+ <term>PAM_SUCCESS</term>
|
||||
+ <listitem>
|
||||
+ <para>
|
||||
+ The condition was true.
|
||||
+ </para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+
|
||||
+ <varlistentry>
|
||||
+ <term>PAM_AUTH_ERR</term>
|
||||
+ <listitem>
|
||||
+ <para>
|
||||
+ The condition was false.
|
||||
+ </para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+
|
||||
+ <varlistentry>
|
||||
+ <term>PAM_SERVICE_ERR</term>
|
||||
+ <listitem>
|
||||
+ <para>
|
||||
+ A service error occurred or the arguments can't be
|
||||
+ parsed correctly.
|
||||
+ </para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+
|
||||
+ <varlistentry>
|
||||
+ <term>PAM_USER_UNKNOWN</term>
|
||||
+ <listitem>
|
||||
+ <para>
|
||||
+ User was not found.
|
||||
+ </para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+ </variablelist>
|
||||
+ </refsect1>
|
||||
+
|
||||
+
|
||||
+ <refsect1 id='pam_usertype-examples'>
|
||||
+ <title>EXAMPLES</title>
|
||||
+ <para>
|
||||
+ Skip remaining modules if the user is a system user:
|
||||
+ </para>
|
||||
+ <programlisting>
|
||||
+account sufficient pam_usertype.so issystem
|
||||
+ </programlisting>
|
||||
+ </refsect1>
|
||||
+
|
||||
+ <refsect1 id='pam_usertype-see_also'>
|
||||
+ <title>SEE ALSO</title>
|
||||
+ <para>
|
||||
+ <citerefentry>
|
||||
+ <refentrytitle>login.defs</refentrytitle><manvolnum>5</manvolnum>
|
||||
+ </citerefentry>,
|
||||
+ <citerefentry>
|
||||
+ <refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum>
|
||||
+ </citerefentry>
|
||||
+ </para>
|
||||
+ </refsect1>
|
||||
+
|
||||
+ <refsect1 id='pam_usertype-author'>
|
||||
+ <title>AUTHOR</title>
|
||||
+ <para>Pavel Březina <pbrezina@redhat.com></para>
|
||||
+ </refsect1>
|
||||
+</refentry>
|
||||
diff --git a/modules/pam_usertype/pam_usertype.c b/modules/pam_usertype/pam_usertype.c
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..d3629c137d98545871d24ff26c06d8377068141f
|
||||
--- /dev/null
|
||||
+++ b/modules/pam_usertype/pam_usertype.c
|
||||
@@ -0,0 +1,319 @@
|
||||
+/******************************************************************************
|
||||
+ * Check user type based on login.defs.
|
||||
+ *
|
||||
+ * Copyright (c) 2020 Red Hat, Inc.
|
||||
+ * Written by Pavel Březina <pbrezina@redhat.com>
|
||||
+ *
|
||||
+ * 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 <sys/types.h>
|
||||
+#include <stdlib.h>
|
||||
+#include <string.h>
|
||||
+#include <syslog.h>
|
||||
+#include <unistd.h>
|
||||
+#include <pwd.h>
|
||||
+#include <ctype.h>
|
||||
+#include <errno.h>
|
||||
+
|
||||
+#define PAM_SM_AUTH
|
||||
+#define PAM_SM_ACCOUNT
|
||||
+#define PAM_SM_SESSION
|
||||
+#define PAM_SM_PASSWORD
|
||||
+
|
||||
+#include <security/pam_modules.h>
|
||||
+#include <security/pam_modutil.h>
|
||||
+#include <security/pam_ext.h>
|
||||
+
|
||||
+#define LOGIN_DEFS "/etc/login.defs"
|
||||
+
|
||||
+enum pam_usertype_op {
|
||||
+ OP_IS_SYSTEM,
|
||||
+ OP_IS_REGULAR,
|
||||
+
|
||||
+ OP_SENTINEL
|
||||
+};
|
||||
+
|
||||
+struct pam_usertype_opts {
|
||||
+ enum pam_usertype_op op;
|
||||
+ int use_uid;
|
||||
+ int audit;
|
||||
+};
|
||||
+
|
||||
+static int
|
||||
+pam_usertype_parse_args(struct pam_usertype_opts *opts,
|
||||
+ pam_handle_t *pamh,
|
||||
+ int argc,
|
||||
+ const char **argv)
|
||||
+{
|
||||
+ int i;
|
||||
+
|
||||
+ memset(opts, 0, sizeof(struct pam_usertype_opts));
|
||||
+ opts->op = OP_SENTINEL;
|
||||
+
|
||||
+ for (i = 0; i < argc; i++) {
|
||||
+ if (strcmp(argv[i], "use_uid") == 0) {
|
||||
+ opts->use_uid = 1;
|
||||
+ } else if (strcmp(argv[i], "audit") == 0) {
|
||||
+ opts->audit = 1;
|
||||
+ } else if (strcmp(argv[i], "issystem") == 0) {
|
||||
+ opts->op = OP_IS_SYSTEM;
|
||||
+ } else if (strcmp(argv[i], "isregular") == 0) {
|
||||
+ opts->op = OP_IS_REGULAR;
|
||||
+ } else {
|
||||
+ pam_syslog(pamh, LOG_WARNING, "Unknown argument: %s", argv[i]);
|
||||
+ /* Just continue. */
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (opts->op == OP_SENTINEL) {
|
||||
+ pam_syslog(pamh, LOG_ERR, "Operation not specified");
|
||||
+ return PAM_SERVICE_ERR;
|
||||
+ }
|
||||
+
|
||||
+ return PAM_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+pam_usertype_get_uid(struct pam_usertype_opts *opts,
|
||||
+ pam_handle_t *pamh,
|
||||
+ uid_t *_uid)
|
||||
+{
|
||||
+ struct passwd *pwd;
|
||||
+ const void *prompt;
|
||||
+ const char *username;
|
||||
+ int ret;
|
||||
+
|
||||
+ /* Get uid of user that runs the application. */
|
||||
+ if (opts->use_uid) {
|
||||
+ pwd = pam_modutil_getpwuid(pamh, getuid());
|
||||
+ if (pwd == NULL) {
|
||||
+ pam_syslog(pamh, LOG_ERR,
|
||||
+ "error retrieving information about user %lu",
|
||||
+ (unsigned long)getuid());
|
||||
+ return PAM_USER_UNKNOWN;
|
||||
+ }
|
||||
+
|
||||
+ *_uid = pwd->pw_uid;
|
||||
+ return PAM_SUCCESS;
|
||||
+ }
|
||||
+
|
||||
+ /* Get uid of user that is being authenticated. */
|
||||
+ ret = pam_get_item(pamh, PAM_USER_PROMPT, &prompt);
|
||||
+ if (ret != PAM_SUCCESS || prompt == NULL || strlen(prompt) == 0) {
|
||||
+ prompt = "login: ";
|
||||
+ }
|
||||
+
|
||||
+ ret = pam_get_user(pamh, &username, prompt);
|
||||
+ if (ret != PAM_SUCCESS || username == NULL) {
|
||||
+ pam_syslog(pamh, LOG_ERR, "error retrieving user name: %s",
|
||||
+ pam_strerror(pamh, ret));
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ pwd = pam_modutil_getpwnam(pamh, username);
|
||||
+ if (pwd == NULL) {
|
||||
+ if (opts->audit) {
|
||||
+ pam_syslog(pamh, LOG_NOTICE,
|
||||
+ "error retrieving information about user %s", username);
|
||||
+ }
|
||||
+
|
||||
+ return PAM_USER_UNKNOWN;
|
||||
+ }
|
||||
+
|
||||
+ *_uid = pwd->pw_uid;
|
||||
+
|
||||
+ return PAM_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+#define MAX_UID_VALUE 0xFFFFFFFFUL
|
||||
+
|
||||
+static uid_t
|
||||
+pam_usertype_get_id(pam_handle_t *pamh,
|
||||
+ const char *key,
|
||||
+ uid_t default_value)
|
||||
+{
|
||||
+ unsigned long ul;
|
||||
+ char *value;
|
||||
+ char *ep;
|
||||
+ uid_t uid;
|
||||
+
|
||||
+ value = pam_modutil_search_key(pamh, LOGIN_DEFS, key);
|
||||
+ if (value == NULL) {
|
||||
+ return default_value;
|
||||
+ }
|
||||
+
|
||||
+ /* taken from get_lastlog_uid_max() */
|
||||
+ ep = value + strlen(value);
|
||||
+ while (ep > value && isspace(*(--ep))) {
|
||||
+ *ep = '\0';
|
||||
+ }
|
||||
+
|
||||
+ errno = 0;
|
||||
+ ul = strtoul(value, &ep, 10);
|
||||
+ if (!(ul >= MAX_UID_VALUE
|
||||
+ || (uid_t)ul >= MAX_UID_VALUE
|
||||
+ || (errno != 0 && ul == 0)
|
||||
+ || value == ep
|
||||
+ || *ep != '\0')) {
|
||||
+ uid = (uid_t)ul;
|
||||
+ } else {
|
||||
+ uid = default_value;
|
||||
+ }
|
||||
+
|
||||
+ free(value);
|
||||
+
|
||||
+ return uid;
|
||||
+}
|
||||
+
|
||||
+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) {
|
||||
+ pam_syslog(pamh, LOG_WARNING, "invalid uid");
|
||||
+ 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;
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+pam_usertype_is_regular(pam_handle_t *pamh, uid_t uid)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = pam_usertype_is_system(pamh, uid);
|
||||
+ switch (ret) {
|
||||
+ case PAM_SUCCESS:
|
||||
+ return PAM_AUTH_ERR;
|
||||
+ case PAM_USER_UNKNOWN:
|
||||
+ return PAM_USER_UNKNOWN;
|
||||
+ default:
|
||||
+ return PAM_SUCCESS;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+pam_usertype_evaluate(struct pam_usertype_opts *opts,
|
||||
+ pam_handle_t *pamh,
|
||||
+ uid_t uid)
|
||||
+{
|
||||
+ switch (opts->op) {
|
||||
+ case OP_IS_SYSTEM:
|
||||
+ return pam_usertype_is_system(pamh, uid);
|
||||
+ case OP_IS_REGULAR:
|
||||
+ return pam_usertype_is_regular(pamh, uid);
|
||||
+ default:
|
||||
+ pam_syslog(pamh, LOG_ERR, "Unknown operation: %d", opts->op);
|
||||
+ return PAM_SERVICE_ERR;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * Arguments:
|
||||
+ * - issystem: uid in <SYS_UID_MIN, 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
|
||||
+ */
|
||||
+int
|
||||
+pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
|
||||
+ int argc, const char **argv)
|
||||
+{
|
||||
+ struct pam_usertype_opts opts;
|
||||
+ uid_t uid;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = pam_usertype_parse_args(&opts, pamh, argc, argv);
|
||||
+ if (ret != PAM_SUCCESS) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ ret = pam_usertype_get_uid(&opts, pamh, &uid);
|
||||
+ if (ret != PAM_SUCCESS) {
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ return pam_usertype_evaluate(&opts, pamh, uid);
|
||||
+}
|
||||
+
|
||||
+int
|
||||
+pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
|
||||
+ int argc UNUSED, const char **argv UNUSED)
|
||||
+{
|
||||
+ return PAM_IGNORE;
|
||||
+}
|
||||
+
|
||||
+int
|
||||
+pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
||||
+{
|
||||
+ return pam_sm_authenticate(pamh, flags, argc, argv);
|
||||
+}
|
||||
+
|
||||
+int
|
||||
+pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
||||
+{
|
||||
+ return pam_sm_authenticate(pamh, flags, argc, argv);
|
||||
+}
|
||||
+
|
||||
+int
|
||||
+pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
||||
+{
|
||||
+ return pam_sm_authenticate(pamh, flags, argc, argv);
|
||||
+}
|
||||
+
|
||||
+int
|
||||
+pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
||||
+{
|
||||
+ return pam_sm_authenticate(pamh, flags, argc, argv);
|
||||
+}
|
||||
diff --git a/modules/pam_usertype/tst-pam_usertype b/modules/pam_usertype/tst-pam_usertype
|
||||
new file mode 100755
|
||||
index 0000000000000000000000000000000000000000..a21f8fe7cef3daf6a842bc35972976ee189d3570
|
||||
--- /dev/null
|
||||
+++ b/modules/pam_usertype/tst-pam_usertype
|
||||
@@ -0,0 +1,2 @@
|
||||
+#!/bin/sh
|
||||
+../../tests/tst-dlopen .libs/pam_usertype.so
|
||||
--
|
||||
2.24.1
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
diff -up Linux-PAM-1.3.1/modules/pam_unix/passverify.c.determinine-user-exists Linux-PAM-1.3.1/modules/pam_unix/passverify.c
|
||||
--- Linux-PAM-1.3.1/modules/pam_unix/passverify.c.determinine-user-exists 2020-06-17 15:34:08.089162532 +0200
|
||||
+++ Linux-PAM-1.3.1/modules/pam_unix/passverify.c 2020-06-17 15:36:13.233294407 +0200
|
||||
@@ -1087,6 +1087,12 @@ helper_verify_password(const char *name,
|
||||
if (pwd == NULL || salt == NULL) {
|
||||
helper_log_err(LOG_NOTICE, "check pass; user unknown");
|
||||
retval = PAM_USER_UNKNOWN;
|
||||
+ } else if (p[0] == '\0' && nullok) {
|
||||
+ if (salt[0] == '\0') {
|
||||
+ retval = PAM_SUCCESS;
|
||||
+ } else {
|
||||
+ retval = PAM_AUTH_ERR;
|
||||
+ }
|
||||
} else {
|
||||
retval = verify_pwd_hash(p, salt, nullok);
|
||||
}
|
||||
diff -up Linux-PAM-1.3.1/modules/pam_unix/support.c.determinine-user-exists Linux-PAM-1.3.1/modules/pam_unix/support.c
|
||||
--- Linux-PAM-1.3.1/modules/pam_unix/support.c.determinine-user-exists 2020-06-17 15:34:08.090162549 +0200
|
||||
+++ Linux-PAM-1.3.1/modules/pam_unix/support.c 2020-06-17 15:34:08.101162736 +0200
|
||||
@@ -672,6 +672,8 @@ _unix_blankpasswd (pam_handle_t *pamh, u
|
||||
struct passwd *pwd = NULL;
|
||||
char *salt = NULL;
|
||||
int retval;
|
||||
+ int execloop = 1;
|
||||
+ int nonexistent = 1;
|
||||
|
||||
D(("called"));
|
||||
|
||||
@@ -686,14 +688,31 @@ _unix_blankpasswd (pam_handle_t *pamh, u
|
||||
|
||||
/* UNIX passwords area */
|
||||
|
||||
- retval = get_pwd_hash(pamh, name, &pwd, &salt);
|
||||
+ /*
|
||||
+ * Execute this loop twice: one checking the password hash of an existing
|
||||
+ * user and another one for a non-existing user. This way the runtimes
|
||||
+ * are equal, making it more difficult to differentiate existing from
|
||||
+ * non-existing users.
|
||||
+ */
|
||||
+ while (execloop) {
|
||||
+ retval = get_pwd_hash(pamh, name, &pwd, &salt);
|
||||
|
||||
- if (retval == PAM_UNIX_RUN_HELPER) {
|
||||
- /* salt will not be set here so we can return immediately */
|
||||
- if (_unix_run_helper_binary(pamh, NULL, ctrl, name) == PAM_SUCCESS)
|
||||
- return 1;
|
||||
- else
|
||||
- return 0;
|
||||
+ if (retval == PAM_UNIX_RUN_HELPER) {
|
||||
+ execloop = 0;
|
||||
+ if(nonexistent) {
|
||||
+ get_pwd_hash(pamh, "pam_unix_non_existent:", &pwd, &salt);
|
||||
+ }
|
||||
+ /* salt will not be set here so we can return immediately */
|
||||
+ if (_unix_run_helper_binary(pamh, NULL, ctrl, name) == PAM_SUCCESS)
|
||||
+ return 1;
|
||||
+ else
|
||||
+ return 0;
|
||||
+ } else if (retval == PAM_USER_UNKNOWN) {
|
||||
+ name = "root";
|
||||
+ nonexistent = 0;
|
||||
+ } else {
|
||||
+ execloop = 0;
|
||||
+ }
|
||||
}
|
||||
|
||||
/* Does this user have a password? */
|
||||
diff -up Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.c.determinine-user-exists Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.c
|
||||
--- Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.c.determinine-user-exists 2020-06-17 15:34:08.098162685 +0200
|
||||
+++ Linux-PAM-1.3.1/modules/pam_usertype/pam_usertype.c 2020-06-17 15:34:08.101162736 +0200
|
||||
@@ -236,8 +236,11 @@ pam_usertype_get_uid(struct pam_usertype
|
||||
"error retrieving information about user %s", username);
|
||||
}
|
||||
|
||||
+ pam_modutil_getpwnam(pamh, "root");
|
||||
+
|
||||
return PAM_USER_UNKNOWN;
|
||||
}
|
||||
+ pam_modutil_getpwnam(pamh, "pam_usertype_non_existent:");
|
||||
|
||||
*_uid = pwd->pw_uid;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
From 62d826471e87e27b39a36ccbeee58999e2514a92 Mon Sep 17 00:00:00 2001
|
||||
From: Allison Karlitskaya <allison.karlitskaya@redhat.com>
|
||||
Date: Thu, 5 Nov 2020 14:06:53 +0100
|
||||
Subject: [PATCH] libpam: add supplementary groups on priv drop
|
||||
|
||||
Replace the setgroups(0, NULL) call in pam_modutil_drop_priv() with a
|
||||
call to initgroups(). This makes sure that the user's supplementary
|
||||
groups are also configured. Fall back to setgroups(0, NULL) in case the
|
||||
initgroups() call fails.
|
||||
|
||||
This fixes the permission check in pam_motd: this feature was intended
|
||||
to allow setting permissions on a motd file to prevent it from being
|
||||
shown to users who are not a member of a particular group (for example,
|
||||
wheel).
|
||||
|
||||
Closes #292
|
||||
---
|
||||
libpam/pam_modutil_priv.c | 17 +++++++++++++----
|
||||
2 files changed, 15 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/libpam/pam_modutil_priv.c b/libpam/pam_modutil_priv.c
|
||||
index e22fab1a..a463e06a 100644
|
||||
--- a/libpam/pam_modutil_priv.c
|
||||
+++ b/libpam/pam_modutil_priv.c
|
||||
@@ -107,11 +107,20 @@ int pam_modutil_drop_priv(pam_handle_t *pamh,
|
||||
* We should care to leave process credentials in consistent state.
|
||||
* That is, e.g. if change_gid() succeeded but change_uid() failed,
|
||||
* we should try to restore old gid.
|
||||
+ *
|
||||
+ * We try to add the supplementary groups on a best-effort
|
||||
+ * basis. If it fails, it's not fatal: we fall back to using an
|
||||
+ * empty list.
|
||||
*/
|
||||
- if (setgroups(0, NULL)) {
|
||||
- pam_syslog(pamh, LOG_ERR,
|
||||
- "pam_modutil_drop_priv: setgroups failed: %m");
|
||||
- return cleanup(p);
|
||||
+ if (initgroups(pw->pw_name, pw->pw_gid)) {
|
||||
+ pam_syslog(pamh, LOG_WARNING,
|
||||
+ "pam_modutil_drop_priv: initgroups failed: %m");
|
||||
+
|
||||
+ if (setgroups(0, NULL)) {
|
||||
+ pam_syslog(pamh, LOG_ERR,
|
||||
+ "pam_modutil_drop_priv: setgroups failed: %m");
|
||||
+ return cleanup(p);
|
||||
+ }
|
||||
}
|
||||
if (change_gid(pw->pw_gid, &p->old_gid)) {
|
||||
pam_syslog(pamh, LOG_ERR,
|
||||
--
|
||||
2.28.0
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
From 395915dae1571e10e2766c999974de864655ea3a Mon Sep 17 00:00:00 2001
|
||||
From: ikerexxe <ipedrosa@redhat.com>
|
||||
Date: Mon, 15 Jun 2020 09:52:11 +0200
|
||||
Subject: [PATCH] pam_faillock: change /run/faillock/$USER permissions to 0660
|
||||
|
||||
Nowadays, /run/faillock/$USER files have user:root ownership and 0600
|
||||
permissions. This forces the process that writes to these files to have
|
||||
CAP_DAC_OVERRIDE capabilites. Just by changing the permissions to 0660
|
||||
the capability can be removed, which leads to a more secure system.
|
||||
|
||||
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1661822
|
||||
---
|
||||
modules/pam_faillock/faillock.c | 14 +++++++++++++-
|
||||
1 file changed, 13 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/modules/pam_faillock/faillock.c b/modules/pam_faillock/faillock.c
|
||||
index e492f5f9..4ea94cbe 100644
|
||||
--- a/modules/pam_faillock/faillock.c
|
||||
+++ b/modules/pam_faillock/faillock.c
|
||||
@@ -76,7 +76,7 @@ open_tally (const char *dir, const char *user, uid_t uid, int create)
|
||||
flags |= O_CREAT;
|
||||
}
|
||||
|
||||
- fd = open(path, flags, 0600);
|
||||
+ fd = open(path, flags, 0660);
|
||||
|
||||
free(path);
|
||||
|
||||
@@ -88,6 +88,18 @@ open_tally (const char *dir, const char *user, uid_t uid, int create)
|
||||
if (st.st_uid != uid) {
|
||||
ignore_return(fchown(fd, uid, -1));
|
||||
}
|
||||
+
|
||||
+ /*
|
||||
+ * If umask is set to 022, as will probably in most systems, then the
|
||||
+ * group will not be able to write to the file. So, change the file
|
||||
+ * permissions just in case.
|
||||
+ * Note: owners of this file are user:root, so if the permissions are
|
||||
+ * not changed the root process writing to this file will require
|
||||
+ * CAP_DAC_OVERRIDE.
|
||||
+ */
|
||||
+ if (!(st.st_mode & S_IWGRP)) {
|
||||
+ ignore_return(fchmod(fd, 0660));
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.26.2
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
diff -up Linux-PAM-1.3.1/modules/pam_motd/pam_motd.8.xml.motd-filter-files Linux-PAM-1.3.1/modules/pam_motd/pam_motd.8.xml
|
||||
--- Linux-PAM-1.3.1/modules/pam_motd/pam_motd.8.xml.motd-filter-files 2020-10-26 11:28:41.528302216 +0100
|
||||
+++ Linux-PAM-1.3.1/modules/pam_motd/pam_motd.8.xml 2020-10-26 11:28:41.542302409 +0100
|
||||
@@ -64,8 +64,9 @@
|
||||
override files with the same name in <filename>/usr/lib/motd.d/</filename>.
|
||||
</para>
|
||||
<para>
|
||||
- Files the in the directories listed above are displayed in
|
||||
- lexicographic order by name.
|
||||
+ 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.
|
||||
</para>
|
||||
<para>
|
||||
To silence a message,
|
||||
diff -up Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c.motd-filter-files Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c
|
||||
--- Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c.motd-filter-files 2020-10-26 11:28:41.541302395 +0100
|
||||
+++ Linux-PAM-1.3.1/modules/pam_motd/pam_motd.c 2020-10-26 11:32:15.933258567 +0100
|
||||
@@ -313,6 +313,71 @@ static void try_to_display_directories_w
|
||||
return;
|
||||
}
|
||||
|
||||
+static int drop_privileges(pam_handle_t *pamh, struct pam_modutil_privs *privs)
|
||||
+{
|
||||
+ struct passwd *pw;
|
||||
+ const char *username;
|
||||
+ int retval;
|
||||
+
|
||||
+ retval = pam_get_user(pamh, &username, "key user");
|
||||
+
|
||||
+ if (retval == PAM_SUCCESS) {
|
||||
+ pw = pam_modutil_getpwnam (pamh, username);
|
||||
+ } else {
|
||||
+ return PAM_SESSION_ERR;
|
||||
+ }
|
||||
+
|
||||
+ if (pw == NULL || pam_modutil_drop_priv(pamh, privs, pw)) {
|
||||
+ return PAM_SESSION_ERR;
|
||||
+ }
|
||||
+
|
||||
+ return PAM_SUCCESS;
|
||||
+}
|
||||
+
|
||||
+static int try_to_display(pam_handle_t *pamh, char **motd_path_split,
|
||||
+ unsigned int num_motd_paths,
|
||||
+ char **motd_dir_path_split,
|
||||
+ unsigned int num_motd_dir_paths)
|
||||
+{
|
||||
+ PAM_MODUTIL_DEF_PRIVS(privs);
|
||||
+
|
||||
+ if (drop_privileges(pamh, &privs) != PAM_SUCCESS) {
|
||||
+ pam_syslog(pamh, LOG_ERR, "Unable to drop privileges");
|
||||
+ return PAM_SESSION_ERR;
|
||||
+ }
|
||||
+
|
||||
+ if (motd_path_split != NULL) {
|
||||
+ unsigned 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);
|
||||
+
|
||||
+ /* We found and displayed a file,
|
||||
+ * move onto next filename.
|
||||
+ */
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (motd_dir_path_split != NULL) {
|
||||
+ try_to_display_directories_with_overrides(pamh,
|
||||
+ motd_dir_path_split,
|
||||
+ num_motd_dir_paths);
|
||||
+ }
|
||||
+
|
||||
+ if (pam_modutil_regain_priv(pamh, &privs)) {
|
||||
+ pam_syslog(pamh, LOG_ERR, "Unable to regain privileges");
|
||||
+ return PAM_SESSION_ERR;
|
||||
+ }
|
||||
+
|
||||
+ return PAM_SUCCESS;
|
||||
+}
|
||||
+
|
||||
int pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||
int argc, const char **argv)
|
||||
{
|
||||
@@ -384,25 +450,8 @@ int pam_sm_open_session(pam_handle_t *pa
|
||||
}
|
||||
}
|
||||
|
||||
- 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);
|
||||
-
|
||||
- /* We found and displayed a file, move onto next filename. */
|
||||
- break;
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- if (motd_dir_path_split != NULL)
|
||||
- try_to_display_directories_with_overrides(pamh, motd_dir_path_split,
|
||||
- num_motd_dir_paths);
|
||||
+ retval = try_to_display(pamh, motd_path_split, num_motd_paths,
|
||||
+ motd_dir_path_split, num_motd_dir_paths);
|
||||
|
||||
out:
|
||||
_pam_drop(motd_path_copy);
|
||||
@@ -410,7 +460,12 @@ int pam_sm_open_session(pam_handle_t *pa
|
||||
_pam_drop(motd_dir_path_copy);
|
||||
_pam_drop(motd_dir_path_split);
|
||||
|
||||
- return retval;
|
||||
+ if (retval == PAM_SUCCESS) {
|
||||
+ retval = pam_putenv(pamh, "MOTD_SHOWN=pam");
|
||||
+ return retval == PAM_SUCCESS ? PAM_IGNORE : retval;
|
||||
+ } else {
|
||||
+ return retval;
|
||||
+ }
|
||||
}
|
||||
|
||||
/* end of module definition */
|
||||
diff -up Linux-PAM-1.3.1/NEWS.motd-filter-files Linux-PAM-1.3.1/NEWS
|
|
@ -0,0 +1,27 @@
|
|||
From 9f24bbeeb4fe04bc396898cd9825478ad52c5ac7 Mon Sep 17 00:00:00 2001
|
||||
From: ikerexxe <ipedrosa@redhat.com>
|
||||
Date: Wed, 21 Oct 2020 09:47:20 +0200
|
||||
Subject: [PATCH] pam_motd: unset prompt value to drop privileges
|
||||
|
||||
modules/pam_motd/pam_motd.c: set NULL value instead of "key user" for the
|
||||
prompt when dropping privileges.
|
||||
---
|
||||
modules/pam_motd/pam_motd.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/modules/pam_motd/pam_motd.c b/modules/pam_motd/pam_motd.c
|
||||
index a4fd0e59..6ac8cba2 100644
|
||||
--- a/modules/pam_motd/pam_motd.c
|
||||
+++ b/modules/pam_motd/pam_motd.c
|
||||
@@ -288,7 +288,7 @@ static int drop_privileges(pam_handle_t *pamh, struct pam_modutil_privs *privs)
|
||||
const char *username;
|
||||
int retval;
|
||||
|
||||
- retval = pam_get_user(pamh, &username, "key user");
|
||||
+ retval = pam_get_user(pamh, &username, NULL);
|
||||
|
||||
if (retval == PAM_SUCCESS) {
|
||||
pw = pam_modutil_getpwnam (pamh, username);
|
||||
--
|
||||
2.26.2
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
From c6c51832af8e7724cfbd454daa65a6644f5b45c2 Mon Sep 17 00:00:00 2001
|
||||
From: ikerexxe <ipedrosa@redhat.com>
|
||||
Date: Fri, 6 Mar 2020 15:04:09 +0100
|
||||
Subject: [PATCH] pam_selinux: check unknown object classes or permissions in
|
||||
current policy
|
||||
|
||||
Explanation: check whether unknown object classes or permissions are allowed or denied in the current policy
|
||||
|
||||
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1680961
|
||||
---
|
||||
modules/pam_selinux/pam_selinux.c | 50 +++++--------------------------
|
||||
1 file changed, 8 insertions(+), 42 deletions(-)
|
||||
|
||||
diff --git a/modules/pam_selinux/pam_selinux.c b/modules/pam_selinux/pam_selinux.c
|
||||
index 96f9c831..827f5942 100644
|
||||
--- a/modules/pam_selinux/pam_selinux.c
|
||||
+++ b/modules/pam_selinux/pam_selinux.c
|
||||
@@ -157,42 +157,6 @@ query_response (pam_handle_t *pamh, const char *text, const char *def,
|
||||
return rc;
|
||||
}
|
||||
|
||||
-static int mls_range_allowed(pam_handle_t *pamh, security_context_t src, security_context_t dst, int debug)
|
||||
-{
|
||||
- struct av_decision avd;
|
||||
- int retval;
|
||||
- security_class_t class;
|
||||
- access_vector_t bit;
|
||||
- context_t src_context;
|
||||
- context_t dst_context;
|
||||
-
|
||||
- class = string_to_security_class("context");
|
||||
- if (!class) {
|
||||
- pam_syslog(pamh, LOG_ERR, "Failed to translate security class context. %m");
|
||||
- return 0;
|
||||
- }
|
||||
-
|
||||
- bit = string_to_av_perm(class, "contains");
|
||||
- if (!bit) {
|
||||
- pam_syslog(pamh, LOG_ERR, "Failed to translate av perm contains. %m");
|
||||
- return 0;
|
||||
- }
|
||||
-
|
||||
- src_context = context_new (src);
|
||||
- dst_context = context_new (dst);
|
||||
- context_range_set(dst_context, context_range_get(src_context));
|
||||
- if (debug)
|
||||
- pam_syslog(pamh, LOG_NOTICE, "Checking if %s mls range valid for %s", dst, context_str(dst_context));
|
||||
-
|
||||
- retval = security_compute_av(context_str(dst_context), dst, class, bit, &avd);
|
||||
- context_free(src_context);
|
||||
- context_free(dst_context);
|
||||
- if (retval || ((bit & avd.allowed) != bit))
|
||||
- return 0;
|
||||
-
|
||||
- return 1;
|
||||
-}
|
||||
-
|
||||
static security_context_t
|
||||
config_context (pam_handle_t *pamh, security_context_t defaultcon, int use_current_range, int debug)
|
||||
{
|
||||
@@ -274,16 +238,17 @@ config_context (pam_handle_t *pamh, security_context_t defaultcon, int use_curre
|
||||
goto fail_set;
|
||||
context_free(new_context);
|
||||
|
||||
- /* we have to check that this user is allowed to go into the
|
||||
- range they have specified ... role is tied to an seuser, so that'll
|
||||
- be checked at setexeccon time */
|
||||
- if (mls_enabled && !mls_range_allowed(pamh, defaultcon, newcon, debug)) {
|
||||
+ /* we have to check that this user is allowed to go into the
|
||||
+ range they have specified ... role is tied to an seuser, so that'll
|
||||
+ be checked at setexeccon time */
|
||||
+ if (mls_enabled &&
|
||||
+ selinux_check_access(defaultcon, newcon, "context", "contains", NULL) != 0) {
|
||||
pam_syslog(pamh, LOG_NOTICE, "Security context %s is not allowed for %s", defaultcon, newcon);
|
||||
|
||||
send_audit_message(pamh, 0, defaultcon, newcon);
|
||||
|
||||
free(newcon);
|
||||
- goto fail_range;
|
||||
+ goto fail_range;
|
||||
}
|
||||
return newcon;
|
||||
}
|
||||
@@ -385,7 +350,8 @@ context_from_env (pam_handle_t *pamh, security_context_t defaultcon, int env_par
|
||||
/* we have to check that this user is allowed to go into the
|
||||
range they have specified ... role is tied to an seuser, so that'll
|
||||
be checked at setexeccon time */
|
||||
- if (mls_enabled && !mls_range_allowed(pamh, defaultcon, newcon, debug)) {
|
||||
+ if (mls_enabled &&
|
||||
+ selinux_check_access(defaultcon, newcon, "context", "contains", NULL) != 0) {
|
||||
pam_syslog(pamh, LOG_NOTICE, "Security context %s is not allowed for %s", defaultcon, newcon);
|
||||
|
||||
goto fail_set;
|
||||
--
|
||||
2.24.1
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
diff -up Linux-PAM-1.3.1/modules/pam_unix/support.c.my Linux-PAM-1.3.1/modules/pam_unix/support.c
|
||||
--- Linux-PAM-1.3.1/modules/pam_unix/support.c.my 2020-11-20 15:43:34.987915063 +0100
|
||||
+++ Linux-PAM-1.3.1/modules/pam_unix/support.c 2020-11-20 15:46:00.950864468 +0100
|
||||
@@ -672,8 +672,9 @@ _unix_blankpasswd (pam_handle_t *pamh, u
|
||||
struct passwd *pwd = NULL;
|
||||
char *salt = NULL;
|
||||
int retval;
|
||||
- int execloop = 1;
|
||||
- int nonexistent = 1;
|
||||
+ int blank = 0;
|
||||
+ int execloop;
|
||||
+ int nonexistent_check = 1;
|
||||
|
||||
D(("called"));
|
||||
|
||||
@@ -694,43 +695,29 @@ _unix_blankpasswd (pam_handle_t *pamh, u
|
||||
* are equal, making it more difficult to differentiate existing from
|
||||
* non-existing users.
|
||||
*/
|
||||
- while (execloop) {
|
||||
+ for (execloop = 0; execloop < 2; ++execloop) {
|
||||
retval = get_pwd_hash(pamh, name, &pwd, &salt);
|
||||
|
||||
if (retval == PAM_UNIX_RUN_HELPER) {
|
||||
- execloop = 0;
|
||||
- if(nonexistent) {
|
||||
- get_pwd_hash(pamh, "pam_unix_non_existent:", &pwd, &salt);
|
||||
- }
|
||||
- /* salt will not be set here so we can return immediately */
|
||||
if (_unix_run_helper_binary(pamh, NULL, ctrl, name) == PAM_SUCCESS)
|
||||
- return 1;
|
||||
- else
|
||||
- return 0;
|
||||
+ blank = nonexistent_check;
|
||||
} else if (retval == PAM_USER_UNKNOWN) {
|
||||
name = "root";
|
||||
- nonexistent = 0;
|
||||
- } else {
|
||||
- execloop = 0;
|
||||
+ nonexistent_check = 0;
|
||||
+ continue;
|
||||
+ } else if (salt != NULL) {
|
||||
+ if (strlen(salt) == 0)
|
||||
+ blank = nonexistent_check;
|
||||
}
|
||||
- }
|
||||
-
|
||||
- /* Does this user have a password? */
|
||||
- if (salt == NULL) {
|
||||
- retval = 0;
|
||||
- } else {
|
||||
- if (strlen(salt) == 0)
|
||||
- retval = 1;
|
||||
- else
|
||||
- retval = 0;
|
||||
+ name = "pam_unix_non_existent:";
|
||||
+ /* non-existent user check will not affect the blank value */
|
||||
}
|
||||
|
||||
/* tidy up */
|
||||
-
|
||||
if (salt)
|
||||
_pam_delete(salt);
|
||||
|
||||
- return retval;
|
||||
+ return blank;
|
||||
}
|
||||
|
||||
int _unix_verify_password(pam_handle_t * pamh, const char *name
|
|
@ -0,0 +1,12 @@
|
|||
diff -up Linux-PAM-1.3.1/modules/pam_unix/pam_unix_acct.c.unix-init-daysleft Linux-PAM-1.3.1/modules/pam_unix/pam_unix_acct.c
|
||||
--- Linux-PAM-1.3.1/modules/pam_unix/pam_unix_acct.c.unix-init-daysleft 2020-11-11 09:33:02.169597139 +0100
|
||||
+++ Linux-PAM-1.3.1/modules/pam_unix/pam_unix_acct.c 2020-11-11 09:39:33.919570928 +0100
|
||||
@@ -188,7 +188,7 @@ pam_sm_acct_mgmt(pam_handle_t *pamh, int
|
||||
unsigned long long ctrl;
|
||||
const void *void_uname;
|
||||
const char *uname;
|
||||
- int retval, daysleft;
|
||||
+ int retval, daysleft = -1;
|
||||
struct spwd *spent;
|
||||
struct passwd *pwent;
|
||||
char buf[256];
|
60
pam.spec
60
pam.spec
|
@ -3,7 +3,7 @@
|
|||
Summary: An extensible library which provides authentication for applications
|
||||
Name: pam
|
||||
Version: 1.3.1
|
||||
Release: 22%{?dist}
|
||||
Release: 30%{?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+.
|
||||
|
@ -60,6 +60,24 @@ Patch48: pam-1.3.1-unix-improve-logging.patch
|
|||
Patch49: pam-1.3.1-tty-audit-manfix.patch
|
||||
Patch50: pam-1.3.1-fds-closing.patch
|
||||
Patch51: pam-1.3.1-authtok-verify-fix.patch
|
||||
Patch52: pam-1.3.1-add-pam_usertype.patch
|
||||
Patch53: pam-1.3.1-add-pam_usertype-fix-backport.patch
|
||||
Patch54: pam-1.3.1-pam_selinux-check-unknown-objects.patch
|
||||
# Upstreamed
|
||||
Patch55: pam-1.3.1-determinine-user-exists.patch
|
||||
# Upstreamed
|
||||
Patch56: pam-1.3.1-faillock-change-file-permissions.patch
|
||||
# https://github.com/linux-pam/linux-pam/commit/16cebfeb30a8bd7c7dc269190a054c25b0f8d044
|
||||
# https://github.com/linux-pam/linux-pam/commit/ad8b6feaf8ea989368676acaea905998a807986e
|
||||
Patch57: pam-1.3.1-motd-filter-files.patch
|
||||
# https://github.com/linux-pam/linux-pam/commit/db6b293046aee4735f3aa2d1713742ed4b533219
|
||||
Patch58: pam-1.3.1-unix-init-daysleft.patch
|
||||
# https://github.com/linux-pam/linux-pam/commit/9f24bbeeb4fe04bc396898cd9825478ad52c5ac7
|
||||
Patch59: pam-1.3.1-motd-privilege-message.patch
|
||||
# https://github.com/linux-pam/linux-pam/commit/30fdfb90d9864bcc254a62760aaa149d373fd4eb
|
||||
Patch60: pam-1.3.1-unix-blank-check-with-root.patch
|
||||
# https://github.com/linux-pam/linux-pam/commit/62d826471e87e27b39a36ccbeee58999e2514a92
|
||||
Patch61: pam-1.3.1-drop-priv-initgroups.patch
|
||||
|
||||
%global _pamlibdir %{_libdir}
|
||||
%global _moduledir %{_libdir}/security
|
||||
|
@ -76,6 +94,7 @@ Patch51: pam-1.3.1-authtok-verify-fix.patch
|
|||
|
||||
Recommends: cracklib-dicts >= 2.8
|
||||
Requires: libpwquality >= 0.9.9
|
||||
BuildRequires: make
|
||||
BuildRequires: autoconf >= 2.60
|
||||
BuildRequires: automake, libtool
|
||||
BuildRequires: bison, flex, sed
|
||||
|
@ -150,6 +169,16 @@ cp %{SOURCE18} .
|
|||
%patch49 -p1 -b .tty-audit-manfix
|
||||
%patch50 -p1 -b .fds-closing
|
||||
%patch51 -p1 -b .authtok-verify-fix
|
||||
%patch52 -p1 -b .add-pam_usertype
|
||||
%patch53 -p1 -b .add-pam_usertype-backport
|
||||
%patch54 -p1 -b .pam_selinux-check-unknown-objects
|
||||
%patch55 -p1 -b .determinine-user-exists
|
||||
%patch56 -p1 -b .faillock-change-file-permissions
|
||||
%patch57 -p1 -b .motd-filter-files
|
||||
%patch58 -p1 -b .unix-init-daysleft
|
||||
%patch59 -p1 -b .motd-privilege-message
|
||||
%patch60 -p1 -b .unix-blank-check-with-root
|
||||
%patch61 -p1 -b .drop-priv-initgroups
|
||||
|
||||
autoreconf -i
|
||||
|
||||
|
@ -354,6 +383,7 @@ done
|
|||
%{_moduledir}/pam_unix_passwd.so
|
||||
%{_moduledir}/pam_unix_session.so
|
||||
%{_moduledir}/pam_userdb.so
|
||||
%{_moduledir}/pam_usertype.so
|
||||
%{_moduledir}/pam_warn.so
|
||||
%{_moduledir}/pam_wheel.so
|
||||
%{_moduledir}/pam_xauth.so
|
||||
|
@ -399,6 +429,34 @@ done
|
|||
%doc doc/specs/rfc86.0.txt
|
||||
|
||||
%changelog
|
||||
* Fri Dec 11 2020 Allison Karlitskaya <allison.karlitskaya@redhat.com> - 1.3.1-30
|
||||
- libpam: add supplementary groups on priv drop (#1896452)
|
||||
- Add BuildRequires: make (#1902520)
|
||||
|
||||
* Fri Nov 27 2020 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-29
|
||||
- fix CVE-2020-27780: authentication bypass when the user doesn't exist
|
||||
and root password is blank (#1901173)
|
||||
|
||||
* Wed Nov 11 2020 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-28
|
||||
- pam_unix: fix missing initialization of daysleft (#1887077)
|
||||
- pam_motd: change privilege message prompt to default (#1861640)
|
||||
|
||||
* Mon Oct 26 2020 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-27
|
||||
- pam_motd: read motd files with target user credentials skipping unreadable ones (#1861640)
|
||||
- Clarify upstreamed patches
|
||||
|
||||
* Wed Jun 24 2020 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-26
|
||||
- pam_faillock: change /run/faillock/$USER permissions to 0660 (#1661822)
|
||||
|
||||
* Mon Jun 22 2020 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-25
|
||||
- pam_unix and pam_usertype: avoid determining if user exists (#1629598)
|
||||
|
||||
* Mon Mar 9 2020 Iker Pedrosa <ipedrosa@redhat.com> - 1.3.1-24
|
||||
- pam_selinux: check unknown object classes or permissions in current policy
|
||||
|
||||
* Tue Feb 4 2020 Pavel Březina <pbrezina@redhat.com> - 1.3.1-23
|
||||
- Add pam_usertype.so
|
||||
|
||||
* Wed Jan 29 2020 Fedora Release Engineering <releng@fedoraproject.org> - 1.3.1-22
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild
|
||||
|
||||
|
|
Loading…
Reference in New Issue