9170895
From ea2d050b1952feb99f86c98255280beb6e589d8c Mon Sep 17 00:00:00 2001
9170895
From: Ludwig Nussel <ludwig.nussel@suse.de>
9170895
Date: Tue, 17 Aug 2010 13:21:44 +0200
9170895
Subject: [PATCH 1/7] pam support for su
9170895
9170895
---
9170895
 configure.ac    |   14 +++
9170895
 src/Makefile.am |    4 +-
9170895
 src/su.c        |  266 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
9170895
 3 files changed, 278 insertions(+), 6 deletions(-)
9170895
9170895
diff --git a/configure.ac b/configure.ac
9170895
index b07a52b..1fb5839 100644
9170895
--- a/configure.ac
9170895
+++ b/configure.ac
9170895
@@ -128,6 +128,20 @@ fi
9170895
 
9170895
 AC_FUNC_FORK
9170895
 
9170895
+AC_ARG_ENABLE(pam, AS_HELP_STRING([--disable-pam],
9170895
+	[Disable PAM support in su (default=auto)]), , [enable_pam=yes])
9170895
+if test "x$enable_pam" != xno; then
9170895
+  AC_CHECK_LIB([pam], [pam_start], [enable_pam=yes], [enable_pam=no])
9170895
+  AC_CHECK_LIB([pam_misc], [misc_conv], [:], [enable_pam=no])
9170895
+  if test "x$enable_pam" != xno; then
9170895
+    AC_DEFINE(USE_PAM, 1, [Define if you want to use PAM])
9170895
+    PAM_LIBS="-lpam -lpam_misc"
9170895
+    AC_SUBST(PAM_LIBS)
9170895
+  fi
9170895
+fi
9170895
+AC_MSG_CHECKING([whether to enable PAM support in su])
9170895
+AC_MSG_RESULT([$enable_pam])
9170895
+
9170895
 optional_bin_progs=
9170895
 AC_CHECK_FUNCS([chroot],
9170895
         gl_ADD_PROG([optional_bin_progs], [chroot]))
9170895
diff --git a/src/Makefile.am b/src/Makefile.am
9170895
index db5359b..154a5ed 100644
9170895
--- a/src/Makefile.am
9170895
+++ b/src/Makefile.am
9170895
@@ -363,8 +363,8 @@ factor_LDADD += $(LIB_GMP)
9170895
 # for getloadavg
9170895
 uptime_LDADD += $(GETLOADAVG_LIBS)
9170895
 
9170895
-# for crypt
9170895
-su_LDADD += $(LIB_CRYPT)
9170895
+# for crypt and pam
9170895
+su_LDADD += $(LIB_CRYPT) $(PAM_LIBS)
9170895
 
9170895
 # for various ACL functions
9170895
 copy_LDADD += $(LIB_ACL)
9170895
diff --git a/src/su.c b/src/su.c
9170895
index f8f5b61..811aad7 100644
9170895
--- a/src/su.c
9170895
+++ b/src/su.c
9170895
@@ -37,6 +37,16 @@
9170895
    restricts who can su to UID 0 accounts.  RMS considers that to
9170895
    be fascist.
9170895
 
9170895
+#ifdef USE_PAM
9170895
+
9170895
+   Actually, with PAM, su has nothing to do with whether or not a
9170895
+   wheel group is enforced by su.  RMS tries to restrict your access
9170895
+   to a su which implements the wheel group, but PAM considers that
9170895
+   to be fascist, and gives the user/sysadmin the opportunity to
9170895
+   enforce a wheel group by proper editing of /etc/pam.d/su
9170895
+
9170895
+#endif
9170895
+
9170895
    Compile-time options:
9170895
    -DSYSLOG_SUCCESS	Log successful su's (by default, to root) with syslog.
9170895
    -DSYSLOG_FAILURE	Log failed su's (by default, to root) with syslog.
9170895
@@ -52,6 +62,13 @@
9170895
 #include <sys/types.h>
9170895
 #include <pwd.h>
9170895
 #include <grp.h>
9170895
+#ifdef USE_PAM
9170895
+#include <security/pam_appl.h>
9170895
+#include <security/pam_misc.h>
9170895
+#include <signal.h>
9170895
+#include <sys/wait.h>
9170895
+#include <sys/fsuid.h>
9170895
+#endif
9170895
 
9170895
 #include "system.h"
9170895
 #include "getpass.h"
9170895
@@ -111,7 +128,9 @@
9170895
 /* The user to become if none is specified.  */
9170895
 #define DEFAULT_USER "root"
9170895
 
9170895
+#ifndef USE_PAM
9170895
 char *crypt (char const *key, char const *salt);
9170895
+#endif
9170895
 
9170895
 static void run_shell (char const *, char const *, char **, size_t)
9170895
      ATTRIBUTE_NORETURN;
9170895
@@ -125,6 +144,11 @@ static bool simulate_login;
9170895
 /* If true, change some environment vars to indicate the user su'd to.  */
9170895
 static bool change_environment;
9170895
 
9170895
+#ifdef USE_PAM
9170895
+static bool _pam_session_opened;
9170895
+static bool _pam_cred_established;
9170895
+#endif
9170895
+
9170895
 static struct option const longopts[] =
9170895
 {
9170895
   {"command", required_argument, NULL, 'c'},
9170895
@@ -200,7 +224,164 @@ log_su (struct passwd const *pw, bool successful)
9170895
 }
9170895
 #endif
9170895
 
9170895
+#ifdef USE_PAM
9170895
+#define PAM_SERVICE_NAME PROGRAM_NAME
9170895
+#define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
9170895
+static sig_atomic_t volatile caught_signal = false;
9170895
+static pam_handle_t *pamh = NULL;
9170895
+static int retval;
9170895
+static struct pam_conv conv =
9170895
+{
9170895
+  misc_conv,
9170895
+  NULL
9170895
+};
9170895
+
9170895
+#define PAM_BAIL_P(a) \
9170895
+  if (retval) \
9170895
+    { \
9170895
+      pam_end (pamh, retval); \
9170895
+      a; \
9170895
+    }
9170895
+
9170895
+static void
9170895
+cleanup_pam (int retcode)
9170895
+{
9170895
+  if (_pam_session_opened)
9170895
+    pam_close_session (pamh, 0);
9170895
+
9170895
+  if (_pam_cred_established)
9170895
+    pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
9170895
+
9170895
+  pam_end(pamh, retcode);
9170895
+}
9170895
+
9170895
+/* Signal handler for parent process.  */
9170895
+static void
9170895
+su_catch_sig (int sig)
9170895
+{
9170895
+  caught_signal = true;
9170895
+}
9170895
+
9170895
+/* Export env variables declared by PAM modules.  */
9170895
+static void
9170895
+export_pamenv (void)
9170895
+{
9170895
+  char **env;
9170895
+
9170895
+  /* This is a copy but don't care to free as we exec later anyways.  */
9170895
+  env = pam_getenvlist (pamh);
9170895
+  while (env && *env)
9170895
+    {
9170895
+      if (putenv (*env) != 0)
9170895
+	xalloc_die ();
9170895
+      env++;
9170895
+    }
9170895
+}
9170895
+
9170895
+static void
9170895
+create_watching_parent (void)
9170895
+{
9170895
+  pid_t child;
9170895
+  sigset_t ourset;
9170895
+  int status = 0;
9170895
+
9170895
+  retval = pam_open_session (pamh, 0);
9170895
+  if (retval != PAM_SUCCESS)
9170895
+    {
9170895
+      cleanup_pam (retval);
9170895
+      error (EXIT_FAILURE, 0, _("cannot not open session: %s"),
9170895
+	     pam_strerror (pamh, retval));
9170895
+    }
9170895
+  else
9170895
+    _pam_session_opened = 1;
9170895
+
9170895
+  child = fork ();
9170895
+  if (child == (pid_t) -1)
9170895
+    {
9170895
+      cleanup_pam (PAM_ABORT);
9170895
+      error (EXIT_FAILURE, errno, _("cannot create child process"));
9170895
+    }
9170895
+
9170895
+  /* the child proceeds to run the shell */
9170895
+  if (child == 0)
9170895
+    return;
9170895
+
9170895
+  /* In the parent watch the child.  */
9170895
+
9170895
+  /* su without pam support does not have a helper that keeps
9170895
+     sitting on any directory so let's go to /.  */
9170895
+  if (chdir ("/") != 0)
9170895
+    error (0, errno, _("warning: cannot change directory to %s"), "/");
9170895
+
9170895
+  sigfillset (&ourset);
9170895
+  if (sigprocmask (SIG_BLOCK, &ourset, NULL))
9170895
+    {
9170895
+      error (0, errno, _("cannot block signals"));
9170895
+      caught_signal = true;
9170895
+    }
9170895
+  if (!caught_signal)
9170895
+    {
9170895
+      struct sigaction action;
9170895
+      action.sa_handler = su_catch_sig;
9170895
+      sigemptyset (&action.sa_mask);
9170895
+      action.sa_flags = 0;
9170895
+      sigemptyset (&ourset);
9170895
+      if (sigaddset (&ourset, SIGTERM)
9170895
+	  || sigaddset (&ourset, SIGALRM)
9170895
+	  || sigaction (SIGTERM, &action, NULL)
9170895
+	  || sigprocmask (SIG_UNBLOCK, &ourset, NULL))
9170895
+	{
9170895
+	  error (0, errno, _("cannot set signal handler"));
9170895
+	  caught_signal = true;
9170895
+	}
9170895
+    }
9170895
+  if (!caught_signal)
9170895
+    {
9170895
+      pid_t pid;
9170895
+      for (;;)
9170895
+	{
9170895
+	  pid = waitpid (child, &status, WUNTRACED);
9170895
+
9170895
+	  if (pid != (pid_t)-1 && WIFSTOPPED (status))
9170895
+	    {
9170895
+	      kill (getpid (), SIGSTOP);
9170895
+	      /* once we get here, we must have resumed */
9170895
+	      kill (pid, SIGCONT);
9170895
+	    }
9170895
+	  else
9170895
+	    break;
9170895
+	}
9170895
+      if (pid != (pid_t)-1)
9170895
+	if (WIFSIGNALED (status))
9170895
+	  status = WTERMSIG (status) + 128;
9170895
+	else
9170895
+	  status = WEXITSTATUS (status);
9170895
+      else
9170895
+	status = 1;
9170895
+    }
9170895
+  else
9170895
+    status = 1;
9170895
+
9170895
+  if (caught_signal)
9170895
+    {
9170895
+      fprintf (stderr, _("\nSession terminated, killing shell..."));
9170895
+      kill (child, SIGTERM);
9170895
+    }
9170895
+
9170895
+  cleanup_pam (PAM_SUCCESS);
9170895
+
9170895
+  if (caught_signal)
9170895
+    {
9170895
+      sleep (2);
9170895
+      kill (child, SIGKILL);
9170895
+      fprintf (stderr, _(" ...killed.\n"));
9170895
+    }
9170895
+  exit (status);
9170895
+}
9170895
+#endif
9170895
+
9170895
 /* Ask the user for a password.
9170895
+   If PAM is in use, let PAM ask for the password if necessary.
9170895
    Return true if the user gives the correct password for entry PW,
9170895
    false if not.  Return true without asking for a password if run by UID 0
9170895
    or if PW has an empty password.  */
9170895
@@ -208,10 +389,52 @@ log_su (struct passwd const *pw, bool successful)
9170895
 static bool
9170895
 correct_password (const struct passwd *pw)
9170895
 {
9170895
+#ifdef USE_PAM
9170895
+  const struct passwd *lpw;
9170895
+  const char *cp;
9170895
+
9170895
+  retval = pam_start (simulate_login ? PAM_SERVICE_NAME_L : PAM_SERVICE_NAME,
9170895
+		      pw->pw_name, &conv, &pamh);
9170895
+  PAM_BAIL_P (return false);
9170895
+
9170895
+  if (isatty (0) && (cp = ttyname (0)) != NULL)
9170895
+    {
9170895
+      const char *tty;
9170895
+
9170895
+      if (strncmp (cp, "/dev/", 5) == 0)
9170895
+	tty = cp + 5;
9170895
+      else
9170895
+	tty = cp;
9170895
+      retval = pam_set_item (pamh, PAM_TTY, tty);
9170895
+      PAM_BAIL_P (return false);
9170895
+    }
9170895
+#if 0 /* Manpage discourages use of getlogin.  */
9170895
+  cp = getlogin ();
9170895
+  if (!(cp && *cp && (lpw = getpwnam (cp)) != NULL && lpw->pw_uid == getuid ()))
9170895
+#endif
9170895
+  lpw = getpwuid (getuid ());
9170895
+  if (lpw && lpw->pw_name)
9170895
+    {
9170895
+      retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
9170895
+      PAM_BAIL_P (return false);
9170895
+    }
9170895
+  retval = pam_authenticate (pamh, 0);
9170895
+  PAM_BAIL_P (return false);
9170895
+  retval = pam_acct_mgmt (pamh, 0);
9170895
+  if (retval == PAM_NEW_AUTHTOK_REQD)
9170895
+    {
9170895
+      /* Password has expired.  Offer option to change it.  */
9170895
+      retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
9170895
+      PAM_BAIL_P (return false);
9170895
+    }
9170895
+  PAM_BAIL_P (return false);
9170895
+  /* Must be authenticated if this point was reached.  */
9170895
+  return true;
9170895
+#else /* !USE_PAM */
9170895
   char *unencrypted, *encrypted, *correct;
9170895
 #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
9170895
   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
9170895
-  struct spwd *sp = getspnam (pw->pw_name);
9170895
+  const struct spwd *sp = getspnam (pw->pw_name);
9170895
 
9170895
   endspent ();
9170895
   if (sp)
9170895
@@ -232,6 +455,7 @@ correct_password (const struct passwd *pw)
9170895
   encrypted = crypt (unencrypted, correct);
9170895
   memset (unencrypted, 0, strlen (unencrypted));
9170895
   return STREQ (encrypted, correct);
9170895
+#endif /* !USE_PAM */
9170895
 }
9170895
 
9170895
 /* Update `environ' for the new shell based on PW, with SHELL being
9170895
@@ -274,19 +498,41 @@ modify_environment (const struct passwd *pw, const char *shell)
9170895
             }
9170895
         }
9170895
     }
9170895
+
9170895
+#ifdef USE_PAM
9170895
+  export_pamenv ();
9170895
+#endif
9170895
 }
9170895
 
9170895
 /* Become the user and group(s) specified by PW.  */
9170895
 
9170895
 static void
9170895
-change_identity (const struct passwd *pw)
9170895
+init_groups (const struct passwd *pw)
9170895
 {
9170895
 #ifdef HAVE_INITGROUPS
9170895
   errno = 0;
9170895
   if (initgroups (pw->pw_name, pw->pw_gid) == -1)
9170895
-    error (EXIT_CANCELED, errno, _("cannot set groups"));
9170895
+    {
9170895
+#ifdef USE_PAM
9170895
+      cleanup_pam (PAM_ABORT);
9170895
+#endif
9170895
+      error (EXIT_FAILURE, errno, _("cannot set groups"));
9170895
+    }
9170895
   endgrent ();
9170895
 #endif
9170895
+
9170895
+#ifdef USE_PAM
9170895
+  retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
9170895
+  if (retval != PAM_SUCCESS)
9170895
+    error (EXIT_FAILURE, 0, "%s", pam_strerror (pamh, retval));
9170895
+  else
9170895
+    _pam_cred_established = 1;
9170895
+#endif
9170895
+}
9170895
+
9170895
+static void
9170895
+change_identity (const struct passwd *pw)
9170895
+{
9170895
   if (setgid (pw->pw_gid))
9170895
     error (EXIT_CANCELED, errno, _("cannot set group id"));
9170895
   if (setuid (pw->pw_uid))
9170895
@@ -500,9 +746,21 @@ main (int argc, char **argv)
9170895
       shell = NULL;
9170895
     }
9170895
   shell = xstrdup (shell ? shell : pw->pw_shell);
9170895
-  modify_environment (pw, shell);
9170895
+
9170895
+  init_groups (pw);
9170895
+
9170895
+#ifdef USE_PAM
9170895
+  create_watching_parent ();
9170895
+  /* Now we're in the child.  */
9170895
+#endif
9170895
 
9170895
   change_identity (pw);
9170895
+
9170895
+  /* Set environment after pam_open_session, which may put KRB5CCNAME
9170895
+     into the pam_env, etc.  */
9170895
+
9170895
+  modify_environment (pw, shell);
9170895
+
9170895
   if (simulate_login && chdir (pw->pw_dir) != 0)
9170895
     error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
9170895
 
9170895
-- 
9170895
1.7.1
9170895
diff -urNp coreutils-8.7-orig/doc/coreutils.texi coreutils-8.7/doc/coreutils.texi
9170895
--- coreutils-8.7-orig/doc/coreutils.texi	2010-11-15 12:47:03.529922880 +0100
9170895
+++ coreutils-8.7/doc/coreutils.texi	2010-11-15 12:49:55.945171380 +0100
9170895
@@ -15180,7 +15180,9 @@ the exit status of @var{command} otherwi
9170895
 
9170895
 @command{su} allows one user to temporarily become another user.  It runs a
9170895
 command (often an interactive shell) with the real and effective user
9170895
-ID, group ID, and supplemental groups of a given @var{user}.  Synopsis:
9170895
+ID, group ID, and supplemental groups of a given @var{user}. When the -l
9170895
+option is given, the su-l PAM file is used instead of the default su PAM file.
9170895
+Synopsis:
9170895
 
9170895
 @example
9170895
 su [@var{option}]@dots{} [@var{user} [@var{arg}]@dots{}]
9170895
@@ -15259,7 +15261,8 @@ environment variables except @env{TERM},
9170895
 (which are set, even for the super-user, as described above), and set
9170895
 @env{PATH} to a compiled-in default value.  Change to @var{user}'s home
9170895
 directory.  Prepend @samp{-} to the shell's name, intended to make it
9170895
-read its login startup file(s).
9170895
+read its login startup file(s). When this option is given, /etc/pam.d/su-l
9170895
+PAM file is used instead of the default one.
9170895
 
9170895
 @item -m
9170895
 @itemx -p