From 69d74f61e8e0b11b45a5262c84da16cb3683df60 Mon Sep 17 00:00:00 2001
From: Thorsten Kukuk <kukuk@suse.com>
Date: Thu, 5 Sep 2019 15:53:52 +0200
Subject: [PATCH 1/1] Add support for a vendor directory and libeconf

With this, it is possible for Linux distributors to store their
supplied default configuration files somewhere below /usr, while
/etc only contains the changes made by the user. The new option
--enable-vendordir defines where Linux-PAM should additional look
for pam.d/*, login.defs and securetty if this files are not in /etc.
libeconf is a key/value configuration file reading library, which
handles the split of configuration files in different locations
and merges them transparently for the application.
---
 Make.xml.rules                            | 10 +++++-----
 configure.ac                              | 17 +++++++++++++++++
 doc/Makefile.am                           |  2 ++
 doc/man/Makefile.am                       |  5 +++++
 doc/man/pam.8.xml                         | 17 +++++++++++++++--
 libpam/Makefile.am                        |  8 ++++++--
 libpam/pam_handlers.c                     | 25 +++++++++++++++++++++++--
 libpam/pam_modutil_searchkey.c            | 30 ++++++++++++++++++++++++++++++
 libpam/pam_private.h                      |  5 +++++
 modules/pam_securetty/Makefile.am         |  8 ++++++++
 modules/pam_securetty/pam_securetty.8.xml | 15 +++++++++------
 modules/pam_securetty/pam_securetty.c     | 28 +++++++++++++++++++++++++---
 12 files changed, 150 insertions(+), 20 deletions(-)

diff --git a/Make.xml.rules b/Make.xml.rules
index bee30cd..d19a02e 100644
--- a/Make.xml.rules
+++ b/Make.xml.rules
@@ -3,22 +3,22 @@
 #
 
 README: README.xml
-	$(XSLTPROC) --path $(srcdir) --xinclude --stringparam generate.toc "none" --nonet http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl $< | $(BROWSER) > $(srcdir)/$@
+	$(XSLTPROC) --path $(srcdir) --xinclude --stringparam generate.toc "none" $(XSLTPROC_CUSTOM) --nonet $(top_srcdir)/doc/custom-html.xsl $< | $(BROWSER) > $(srcdir)/$@
 
 %.1: %.1.xml
 	$(XMLLINT) --nonet --xinclude --postvalid --noout $<
-	$(XSLTPROC) -o $(srcdir)/$@ --path $(srcdir) --xinclude --nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+	$(XSLTPROC) -o $(srcdir)/$@ --path $(srcdir) --xinclude $(XSLTPROC_CUSTOM) --nonet $(top_srcdir)/doc/custom-man.xsl $<
 
 %.3: %.3.xml
 	$(XMLLINT) --nonet --xinclude --postvalid --noout $<
-	$(XSLTPROC) -o $(srcdir)/$@ --path $(srcdir) --xinclude --nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+	$(XSLTPROC) -o $(srcdir)/$@ --path $(srcdir) --xinclude $(XSLTPROC_CUSTOM) --nonet $(top_srcdir)/doc/custom-man.xsl $<
 
 %.5: %.5.xml
 	$(XMLLINT) --nonet --xinclude --postvalid --noout $<
-	$(XSLTPROC) -o $(srcdir)/$@ --path $(srcdir) --xinclude --nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+	$(XSLTPROC) -o $(srcdir)/$@ --path $(srcdir) --xinclude $(XSLTPROC_CUSTOM) --nonet $(top_srcdir)/doc/custom-man.xsl $<
 
 %.8: %.8.xml
 	$(XMLLINT) --nonet --xinclude --postvalid --noout $<
-	$(XSLTPROC) -o $(srcdir)/$@ --path $(srcdir) --xinclude --nonet http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $<
+	$(XSLTPROC) -o $(srcdir)/$@ --path $(srcdir) --xinclude $(XSLTPROC_CUSTOM) --nonet $(top_srcdir)/doc/custom-man.xsl $<
 
 #CLEANFILES += $(man_MANS) README
diff --git a/configure.ac b/configure.ac
index e4995fc..c557d2c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -504,6 +504,23 @@ if test ! -z "$LIBSELINUX" ; then
     LIBS=$BACKUP_LIBS
 fi
 
+AC_ARG_ENABLE([econf],
+  AS_HELP_STRING([--disable-econf], [do not use libeconf]),
+  [WITH_ECONF=$enableval], WITH_ECONF=yes)
+if test "$WITH_ECONF" = "yes" ; then
+  PKG_CHECK_MODULES([ECONF], [libeconf], [],
+  [AC_CHECK_LIB([econf],[econf_readDirs],[ECONF_LIBS="-leconf"],[ECONF_LIBS=""])])
+  if test -n "$ECONF_LIBS" ; then
+    ECONF_CFLAGS="-DUSE_ECONF=1 $ECONF_CFLAGS"
+  fi
+fi
+AC_SUBST([ECONF_CFLAGS])
+AC_SUBST([ECONF_LIBS])
+AC_ARG_ENABLE([vendordir],
+  AS_HELP_STRING([--enable-vendordir=DIR], [Direcotry for istribution provided configuration files]),,[])
+AC_SUBST([VENDORDIR], [$enable_vendordir])
+AM_CONDITIONAL([HAVE_VENDORDIR], [test "x$enable_vendordir" != x])
+
 dnl Checks for header files.
 AC_HEADER_DIRENT
 AC_HEADER_STDC
diff --git a/doc/Makefile.am b/doc/Makefile.am
index f4762f2..38319f5 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -8,6 +8,8 @@ CLEANFILES = *~
 
 dist_html_DATA = index.html
 
+EXTRA_DIST = custom-html.xsl custom-man.xsl
+
 #######################################################
 
 releasedocs: all
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am
index 78c891d..8e76897 100644
--- a/doc/man/Makefile.am
+++ b/doc/man/Makefile.am
@@ -59,5 +59,10 @@ pam.d.5: pam.conf.5
 pam_get_item.3: pam_item_types_std.inc.xml pam_item_types_ext.inc.xml
 pam_set_data.3: pam_item_types_std.inc.xml pam_item_types_ext.inc.xml
 pam.conf.5: pam.conf-desc.xml pam.conf-dir.xml pam.conf-syntax.xml
+if HAVE_VENDORDIR
+XSLTPROC_CUSTOM = --stringparam vendordir $(VENDORDIR)
+else
+XSLTPROC_CUSTOM = --stringparam vendordir "<vendordir>"
+endif
 -include $(top_srcdir)/Make.xml.rules
 endif
diff --git a/doc/man/pam.8.xml b/doc/man/pam.8.xml
index 9839def..ce2d91b 100644
--- a/doc/man/pam.8.xml
+++ b/doc/man/pam.8.xml
@@ -53,11 +53,12 @@
 
     <para>
       Vendor-supplied PAM configuration files might be installed in
-      the system directory <filename>/usr/lib/pam.d/</filename> instead
+      the system directory <filename>/usr/lib/pam.d/</filename> or
+      an configurable vendor specific directory instead
       of the machine configuration directory <filename>/etc/pam.d/</filename>.
       If no machine configuration file is found, the vendor-supplied file
       is used. All files in <filename>/etc/pam.d/</filename> override
-      files with the same name in <filename>/usr/lib/pam.d/</filename>.
+      files with the same name in other directories.
     </para>
 
 <para>From the point of view of the system administrator, for whom this
@@ -157,6 +158,18 @@ closing hook for modules to affect the services available to a user.</para>
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+      	<term><filename>%vendordir%/pam.d</filename></term>
+        <listitem>
+          <para>
+            the <emphasis remap='B'>Linux-PAM</emphasis> vendor configuration
+	    directory. Files in <filename>/etc/pam.d</filename> and
+	    <filename>/usr/lib/pam.d</filename> override files with the same
+	    name in this directory. Only available if Linux-PAM was compiled
+	    with vendordir enabled.
+          </para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
diff --git a/libpam/Makefile.am b/libpam/Makefile.am
index 875031e..1c59140 100644
--- a/libpam/Makefile.am
+++ b/libpam/Makefile.am
@@ -3,10 +3,14 @@
 #
 
 AM_CFLAGS = -DDEFAULT_MODULE_PATH=\"$(SECUREDIR)/\" -DLIBPAM_COMPILE \
-	-I$(srcdir)/include $(LIBPRELUDE_CFLAGS) -DPAM_VERSION=\"$(VERSION)\"
+	-I$(srcdir)/include $(LIBPRELUDE_CFLAGS) \
+	-DPAM_VERSION=\"$(VERSION)\" @ECONF_CFLAGS@
 if HAVE_LIBSELINUX
   AM_CFLAGS += -D"WITH_SELINUX"
 endif
+if HAVE_VENDORDIR
+  AM_CFLAGS += -DVENDORDIR=\"@VENDORDIR@\"
+endif
 
 CLEANFILES = *~
 
@@ -21,7 +25,7 @@ noinst_HEADERS = pam_prelude.h pam_private.h pam_tokens.h \
 		pam_modutil_private.h
 
 libpam_la_LDFLAGS = -no-undefined -version-info 84:2:84
-libpam_la_LIBADD = @LIBAUDIT@ $(LIBPRELUDE_LIBS) @LIBDL@
+libpam_la_LIBADD = @LIBAUDIT@ $(LIBPRELUDE_LIBS) @LIBDL@ @ECONF_LIBS@
 
 if HAVE_VERSIONING
   libpam_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libpam.map
diff --git a/libpam/pam_handlers.c b/libpam/pam_handlers.c
index 106ef7c..51a5f7b 100644
--- a/libpam/pam_handlers.c
+++ b/libpam/pam_handlers.c
@@ -282,7 +282,6 @@ _pam_open_config_file(pam_handle_t *pamh
 {
     char *p;
     FILE *f;
-    int err = 0;
 
     /* Absolute path */
     if (service[0] == '/') {
@@ -329,6 +328,23 @@ _pam_open_config_file(pam_handle_t *pamh
 	    *file = f;
 	    return PAM_SUCCESS;
     }
+
+#ifdef PAM_CONFIG_DIST2_D
+    /* System Configuration /<vendordir>/pam.d/ */
+    _pam_drop(p);
+    if (asprintf (&p, PAM_CONFIG_DIST2_DF, service) < 0) {
+        pam_syslog(pamh, LOG_CRIT, "asprintf failed");
+        return PAM_BUF_ERR;
+    }
+    D(("opening %s", p));
+    f = fopen(p, "r");
+    if (f != NULL) {
+            *path = p;
+            *file = f;
+            return PAM_SUCCESS;
+    }
+#endif
+
     _pam_drop(p);
 
     return PAM_ABORT;
@@ -447,7 +463,12 @@ int _pam_init_handlers(pam_handle_t *pamh)
 
 	/* Is there a PAM_CONFIG_D directory? */
 	if ((stat(PAM_CONFIG_D, &test_d) == 0 && S_ISDIR(test_d.st_mode)) ||
-	    (stat(PAM_CONFIG_DIST_D, &test_d) == 0 && S_ISDIR(test_d.st_mode))) {
+	    (stat(PAM_CONFIG_DIST_D, &test_d) == 0 && S_ISDIR(test_d.st_mode))
+#ifdef PAM_CONFIG_DIST2_D
+	    || (stat(PAM_CONFIG_DIST2_D, &test_d) == 0
+		&& S_ISDIR(test_d.st_mode))
+#endif
+	   ) {
 	    char *path = NULL;
 	    int read_something=0;
 
diff --git a/libpam/pam_modutil_searchkey.c b/libpam/pam_modutil_searchkey.c
index 338b44f..1daed8d 100644
--- a/libpam/pam_modutil_searchkey.c
+++ b/libpam/pam_modutil_searchkey.c
@@ -13,9 +13,34 @@
 #include <string.h>
 #include <stdlib.h>
 #include <ctype.h>
+#ifdef USE_ECONF
+#include <libeconf.h>
+#endif
 
 #define BUF_SIZE 8192
 
+#ifdef USE_ECONF
+#define LOGIN_DEFS "/etc/login.defs"
+
+static char *
+econf_search_key (const char *name, const char *suffix, const char *key)
+{
+  econf_file *key_file = NULL;
+  char *val;
+
+  if (econf_readDirs (&key_file, "/usr/etc", "/etc", name, suffix, " \t", "#"))
+    return NULL;
+
+  if (econf_getStringValue (key_file, NULL, key, &val))
+    return NULL;
+
+  econf_free (key_file);
+
+  return val;
+}
+
+#endif
+
 /* lookup a value for key in login.defs file or similar key value format */
 char *
 pam_modutil_search_key(pam_handle_t *pamh UNUSED,
@@ -27,6 +52,11 @@ pam_modutil_search_key(pam_handle_t *pamh UNUSED,
 	size_t buflen = 0;
 	char *retval = NULL;
 
+#ifdef USE_ECONF
+	if (strcmp (file_name, LOGIN_DEFS) == 0)
+	  return econf_search_key ("login", ".defs", key);
+#endif
+
 	fp = fopen(file_name, "r");
 	if (NULL == fp)
 		return NULL;
diff --git a/libpam/pam_private.h b/libpam/pam_private.h
index 58a26f5..8cb7752 100644
--- a/libpam/pam_private.h
+++ b/libpam/pam_private.h
@@ -29,6 +29,11 @@
 #define PAM_CONFIG_DF      "/etc/pam.d/%s"
 #define PAM_CONFIG_DIST_D  "/usr/lib/pam.d"
 #define PAM_CONFIG_DIST_DF "/usr/lib/pam.d/%s"
+#ifdef VENDORDIR
+#define PAM_CONFIG_DIST2_D  VENDORDIR"/pam.d"
+#define PAM_CONFIG_DIST2_DF VENDORDIR"/pam.d/%s"
+#endif
+
 
 #define PAM_DEFAULT_SERVICE        "other"     /* lower case */
 
diff --git a/modules/pam_securetty/Makefile.am b/modules/pam_securetty/Makefile.am
index 30cc879..94ba04d 100644
--- a/modules/pam_securetty/Makefile.am
+++ b/modules/pam_securetty/Makefile.am
@@ -20,6 +20,9 @@ AM_LDFLAGS = -no-undefined -avoid-version -module
 if HAVE_VERSIONING
   AM_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map
 endif
+if HAVE_VENDORDIR
+  AM_CFLAGS += -DVENDORDIR=\"@VENDORDIR@\"
+endif
 
 securelib_LTLIBRARIES = pam_securetty.la
 pam_securetty_la_LIBADD = $(top_builddir)/libpam/libpam.la
@@ -27,5 +30,10 @@ pam_securetty_la_LIBADD = $(top_builddir)/libpam/libpam.la
 if ENABLE_REGENERATE_MAN
 noinst_DATA = README
 README: pam_securetty.8.xml
+if HAVE_VENDORDIR
+XSLTPROC_CUSTOM = --stringparam vendordir $(VENDORDIR)
+else
+XSLTPROC_CUSTOM = --stringparam vendordir "<vendordir>"
+endif
 -include $(top_srcdir)/Make.xml.rules
 endif
diff --git a/modules/pam_securetty/pam_securetty.8.xml b/modules/pam_securetty/pam_securetty.8.xml
index 48215f5..ce6b6f4 100644
--- a/modules/pam_securetty/pam_securetty.8.xml
+++ b/modules/pam_securetty/pam_securetty.8.xml
@@ -31,9 +31,12 @@
     <para>
       pam_securetty is a PAM module that allows root logins only if the
       user is logging in on a "secure" tty, as defined by the listing
-      in <filename>/etc/securetty</filename>. pam_securetty also checks
-      to make sure that <filename>/etc/securetty</filename> is a plain
-      file and not world writable. It will also allow root logins on
+      in the <filename>securetty</filename> file. pam_securetty checks at
+      first, if <filename>/etc/securetty</filename> exists. If not and
+      it was build with vendordir support, it will use
+      <filename>%vendordir%/securetty</filename>. pam_securetty also
+      checks that the <filename>securetty</filename> files are plain
+      files and not world writable. It will also allow root logins on
       the tty specified with <option>console=</option> switch on the
       kernel command line and on ttys from the
       <filename>/sys/class/tty/console/active</filename>.
@@ -73,7 +76,7 @@
             Do not automatically allow root logins on the kernel console
             device, as specified on the kernel command line or by the sys file,
             if it is not also specified in the
-            <filename>/etc/securetty</filename> file.
+            <filename>securetty</filename> file.
           </para>
         </listitem>
       </varlistentry>
@@ -106,7 +109,7 @@
           <para>
             Authentication is rejected. Either root is attempting to
             log in via an unacceptable device, or the
-            <filename>/etc/securetty</filename> file is world writable or
+            <filename>securetty</filename> file is world writable or
             not a normal file.
           </para>
         </listitem>
@@ -127,7 +130,7 @@
           <para>
             An error occurred while the module was determining the
             user's name or tty, or the module could not open
-            <filename>/etc/securetty</filename>.
+            the <filename>securetty</filename> file.
           </para>
         </listitem>
       </varlistentry>
diff --git a/modules/pam_securetty/pam_securetty.c b/modules/pam_securetty/pam_securetty.c
index cb1da25..e8a9273 100644
--- a/modules/pam_securetty/pam_securetty.c
+++ b/modules/pam_securetty/pam_securetty.c
@@ -1,6 +1,9 @@
 /* pam_securetty module */
 
 #define SECURETTY_FILE "/etc/securetty"
+#ifdef VENDORDIR
+#define SECURETTY2_FILE VENDORDIR"/securetty"
+#endif
 #define TTY_PREFIX     "/dev/"
 #define CMDLINE_FILE   "/proc/cmdline"
 #define CONSOLEACTIVE_FILE	"/sys/class/tty/console/active"
@@ -25,6 +28,7 @@
 #include <string.h>
 #include <ctype.h>
 #include <limits.h>
+#include <errno.h>
 
 /*
  * here, we make a definition for the externally accessible function
@@ -70,6 +74,7 @@ securetty_perform_check (pam_handle_t *pamh, int ctrl,
 			 const char *function_name)
 {
     int retval = PAM_AUTH_ERR;
+    const char *securettyfile;
     const char *username;
     const char *uttyname;
     const void *void_uttyname;
@@ -111,10 +116,27 @@ securetty_perform_check (pam_handle_t *pamh, int ctrl,
     }
 
     if (stat(SECURETTY_FILE, &ttyfileinfo)) {
+#ifdef VENDORDIR
+      if (errno == ENOENT) {
+	if (stat(SECURETTY2_FILE, &ttyfileinfo)) {
+	  pam_syslog(pamh, LOG_NOTICE,
+		     "Couldn't open %s: %m", SECURETTY2_FILE);
+	  return PAM_SUCCESS; /* for compatibility with old securetty handling,
+				 this needs to succeed.  But we still log the
+				 error. */
+	}
+	securettyfile = SECURETTY2_FILE;
+      } else {
+#endif
 	pam_syslog(pamh, LOG_NOTICE, "Couldn't open %s: %m", SECURETTY_FILE);
 	return PAM_SUCCESS; /* for compatibility with old securetty handling,
 			       this needs to succeed.  But we still log the
 			       error. */
+#ifdef VENDORDIR
+      }
+#endif
+    } else {
+      securettyfile = SECURETTY_FILE;
     }
 
     if ((ttyfileinfo.st_mode & S_IWOTH) || !S_ISREG(ttyfileinfo.st_mode)) {
@@ -122,13 +144,13 @@ securetty_perform_check (pam_handle_t *pamh, int ctrl,
 	   normal file, return error */
 	pam_syslog(pamh, LOG_ERR,
 		   "%s is either world writable or not a normal file",
-		   SECURETTY_FILE);
+		   securettyfile);
 	return PAM_AUTH_ERR;
     }
 
-    ttyfile = fopen(SECURETTY_FILE,"r");
+    ttyfile = fopen(securettyfile,"r");
     if (ttyfile == NULL) { /* Check that we opened it successfully */
-	pam_syslog(pamh, LOG_ERR, "Error opening %s: %m", SECURETTY_FILE);
+	pam_syslog(pamh, LOG_ERR, "Error opening %s: %m", securettyfile);
 	return PAM_SERVICE_ERR;
     }
 
-- 
2.16.4

