Index: configure.ac
===================================================================
--- configure.ac.orig	2010-08-31 14:57:39.223339438 +0200
+++ configure.ac	2010-08-31 14:57:40.022841481 +0200
@@ -46,6 +46,20 @@ coreutils_MACROS
 
 AC_FUNC_FORK
 
+AC_ARG_ENABLE(pam, AS_HELP_STRING([--disable-pam],
+	[Enable PAM support in su (default=auto)]), , [enable_pam=yes])
+if test "x$enable_pam" != xno; then
+  AC_CHECK_LIB([pam], [pam_start], [enable_pam=yes], [enable_pam=no])
+  AC_CHECK_LIB([pam_misc], [misc_conv], [:], [enable_pam=no])
+  if test "x$enable_pam" != xno; then
+    AC_DEFINE(USE_PAM, 1, [Define if you want to use PAM])
+    PAM_LIBS="-lpam -lpam_misc"
+    AC_SUBST(PAM_LIBS)
+  fi
+fi
+AC_MSG_CHECKING([whether to enable PAM support in su])
+AC_MSG_RESULT([$enable_pam])
+
 optional_bin_progs=
 AC_CHECK_FUNCS(uname,
 	gl_ADD_PROG([optional_bin_progs], [uname]))
Index: src/Makefile.am
===================================================================
--- src/Makefile.am.orig	2010-08-31 14:57:39.273845915 +0200
+++ src/Makefile.am	2010-08-31 14:58:13.986065031 +0200
@@ -132,7 +132,8 @@ tail_LDADD = $(nanosec_libs)
 # If necessary, add -lm to resolve use of pow in lib/strtod.c.
 uptime_LDADD = $(LDADD) $(POW_LIB) $(GETLOADAVG_LIBS)
 
-su_LDADD = $(LDADD) $(LIB_CRYPT)
+su_SOURCES = su.c getdef.c
+su_LDADD = $(LDADD) $(LIB_CRYPT) $(PAM_LIBS)
 
 dir_LDADD += $(LIB_ACL)
 ls_LDADD += $(LIB_ACL)
Index: src/getdef.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ src/getdef.c	2010-08-31 14:57:40.106339646 +0200
@@ -0,0 +1,259 @@
+/* Copyright (C) 2003, 2004, 2005 Thorsten Kukuk
+   Author: Thorsten Kukuk <kukuk@suse.de>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License version 2 as
+   published by the Free Software Foundation.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "getdef.h"
+
+struct item {
+  char *name;         /* Name of the option.  */
+  char *value;        /* Value of the option.  */
+  struct item *next;  /* Pointer to next option.  */
+};
+
+static struct item *list = NULL;
+
+void
+free_getdef_data (void)
+{
+  struct item *ptr;
+
+  ptr = list;
+  while (ptr != NULL)
+    {
+      struct item *tmp;
+      tmp = ptr->next;
+      free (ptr->name);
+      free (ptr->value);
+      free (ptr);
+      ptr = tmp;
+    }
+
+  list = NULL;
+}
+
+/* Add a new entry to the list.  */
+static void
+store (const char *name, const char *value)
+{
+  struct item *new = malloc (sizeof (struct item));
+
+  if (new == NULL)
+    abort ();
+
+  if (name == NULL)
+    abort ();
+
+  new->name = strdup (name);
+  new->value = strdup (value ?: "");
+  new->next = list;
+  list = new;
+}
+
+/* Search a special entry in the list and return the value.  */
+static const char *
+search (const char *name)
+{
+  struct item *ptr;
+
+  ptr = list;
+  while (ptr != NULL)
+    {
+      if (strcasecmp (name, ptr->name) == 0)
+	return ptr->value;
+      ptr = ptr->next;
+    }
+
+  return NULL;
+}
+
+/* Load the login.defs file (/etc/login.defs).  */
+static void
+load_defaults_internal (const char *filename)
+{
+  FILE *fp;
+  char *buf = NULL;
+  size_t buflen = 0;
+
+  fp = fopen (filename, "r");
+  if (NULL == fp)
+    return;
+
+  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 = 8096;
+          buf = malloc (buflen);
+        }
+      buf[0] = '\0';
+      fgets (buf, buflen - 1, fp);
+      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 ((unsigned char) *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 ((unsigned char) *cp) || *cp == '=')
+	  ++cp;
+
+      store (tmp, cp);
+    }
+  fclose (fp);
+
+  if (buf)
+    free (buf);
+}
+
+static void
+load_defaults (void)
+{
+  load_defaults_internal ("/etc/default/su");
+  load_defaults_internal ("/etc/login.defs");
+}
+
+int
+getdef_bool (const char *name, int dflt)
+{
+  const char *val;
+
+  if (list == NULL)
+    load_defaults ();
+
+  val = search (name);
+
+  if (val == NULL)
+    return dflt;
+
+  return (strcasecmp (val, "yes") == 0);
+}
+
+long
+getdef_num (const char *name, long dflt)
+{
+  const char *val;
+  char *cp;
+  long retval;
+
+  if (list == NULL)
+    load_defaults ();
+
+  val = search (name);
+
+  if (val == NULL)
+    return dflt;
+
+  errno = 0;
+  retval = strtol (val, &cp, 0);
+  if (*cp != '\0'
+      || ((retval == LONG_MAX || retval == LONG_MIN) && errno == ERANGE))
+    {
+      fprintf (stderr,
+	       "%s contains invalid numerical value: %s!\n",
+	       name, val);
+      retval = dflt;
+    }
+  return retval;
+}
+
+unsigned long
+getdef_unum (const char *name, unsigned long dflt)
+{
+  const char *val;
+  char *cp;
+  unsigned long retval;
+
+  if (list == NULL)
+    load_defaults ();
+
+  val = search (name);
+
+  if (val == NULL)
+    return dflt;
+
+  errno = 0;
+  retval = strtoul (val, &cp, 0);
+  if (*cp != '\0' || (retval == ULONG_MAX && errno == ERANGE))
+    {
+      fprintf (stderr,
+	       "%s contains invalid numerical value: %s!\n",
+	       name, val);
+      retval = dflt;
+    }
+  return retval;
+}
+
+const char *
+getdef_str (const char *name, const char *dflt)
+{
+  const char *retval;
+
+  if (list == NULL)
+    load_defaults ();
+
+  retval = search (name);
+
+  return retval ?: dflt;
+}
+
+#if defined(TEST)
+
+int
+main ()
+{
+  printf ("CYPT=%s\n", getdef_str ("cRypt", "no"));
+  printf ("LOG_UNKFAIL_ENAB=%s\n", getdef_str ("log_unkfail_enab",""));
+  printf ("DOESNOTEXIST=%s\n", getdef_str ("DOESNOTEXIST","yes"));
+  return 0;
+}
+
+#endif
Index: src/getdef.h
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ src/getdef.h	2010-08-31 14:57:40.141849207 +0200
@@ -0,0 +1,29 @@
+/* Copyright (C) 2003, 2005 Thorsten Kukuk
+   Author: Thorsten Kukuk <kukuk@suse.de>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License version 2 as
+   published by the Free Software Foundation.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software Foundation,
+   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+#ifndef _GETDEF_H_
+
+#define _GETDEF_H_ 1
+
+extern int getdef_bool (const char *name, int dflt);
+extern long getdef_num (const char *name, long dflt);
+extern unsigned long getdef_unum (const char *name, unsigned long dflt);
+extern const char *getdef_str (const char *name, const char *dflt);
+
+/* Free all data allocated by getdef_* calls before.  */
+extern void free_getdef_data (void);
+
+#endif /* _GETDEF_H_ */
Index: src/su.c
===================================================================
--- src/su.c.orig	2008-05-26 08:40:33.000000000 +0200
+++ src/su.c	2010-08-31 14:57:40.175339256 +0200
@@ -37,6 +37,16 @@
    restricts who can su to UID 0 accounts.  RMS considers that to
    be fascist.
 
+#ifdef USE_PAM
+
+   Actually, with PAM, su has nothing to do with whether or not a
+   wheel group is enforced by su.  RMS tries to restrict your access
+   to a su which implements the wheel group, but PAM considers that
+   to be fascist, and gives the user/sysadmin the opportunity to
+   enforce a wheel group by proper editing of /etc/pam.d/su
+
+#endif
+
    Compile-time options:
    -DSYSLOG_SUCCESS	Log successful su's (by default, to root) with syslog.
    -DSYSLOG_FAILURE	Log failed su's (by default, to root) with syslog.
@@ -52,6 +62,13 @@
 #include <sys/types.h>
 #include <pwd.h>
 #include <grp.h>
+#ifdef USE_PAM
+#include <security/pam_appl.h>
+#include <security/pam_misc.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/fsuid.h>
+#endif
 
 /* Hide any system prototype for getusershell.
    This is necessary because some Cray systems have a conflicting
@@ -65,6 +82,9 @@
 
 #if HAVE_SYSLOG_H && HAVE_SYSLOG
 # include <syslog.h>
+# define SYSLOG_SUCCESS  1
+# define SYSLOG_FAILURE  1
+# define SYSLOG_NON_ROOT 1
 #else
 # undef SYSLOG_SUCCESS
 # undef SYSLOG_FAILURE
@@ -98,19 +118,13 @@
 # include <paths.h>
 #endif
 
+#include "getdef.h"
+
 /* The default PATH for simulated logins to non-superuser accounts.  */
-#ifdef _PATH_DEFPATH
-# define DEFAULT_LOGIN_PATH _PATH_DEFPATH
-#else
-# define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
-#endif
+#define DEFAULT_LOGIN_PATH "/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin"
 
 /* The default PATH for simulated logins to superuser accounts.  */
-#ifdef _PATH_DEFPATH_ROOT
-# define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT
-#else
-# define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
-#endif
+#define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin:/usr/X11R6/bin"
 
 /* The shell to run if none is given in the user's passwd entry.  */
 #define DEFAULT_SHELL "/bin/sh"
@@ -118,13 +132,22 @@
 /* The user to become if none is specified.  */
 #define DEFAULT_USER "root"
 
+#ifndef USE_PAM
 char *crypt ();
+#endif
 char *getusershell ();
 void endusershell ();
 void setusershell ();
 
 extern char **environ;
 
+#ifdef USE_PAM
+static bool _pam_session_opened;
+static bool _pam_cred_established;
+static void export_pamenv(void);
+static void create_watching_parent(void);
+#endif
+
 static void run_shell (char const *, char const *, char **, size_t)
      ATTRIBUTE_NORETURN;
 
@@ -215,7 +238,162 @@ log_su (struct passwd const *pw, bool su
 }
 #endif
 
+#ifdef USE_PAM
+#define PAM_SERVICE_NAME PROGRAM_NAME
+#define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
+static bool caught_signal = false;
+static pam_handle_t *pamh = NULL;
+static int retval;
+static struct pam_conv conv =
+{
+  misc_conv,
+  NULL
+};
+
+#define PAM_BAIL_P(a) \
+  if (retval) \
+    { \
+      pam_end (pamh, retval); \
+      a; \
+    }
+
+static void
+cleanup_pam (int retcode)
+{
+  if (_pam_session_opened)
+    pam_close_session (pamh, 0);
+
+  if (_pam_cred_established)
+    pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
+
+  pam_end(pamh, retcode);
+}
+
+/* Signal handler for parent process.  */
+static void
+su_catch_sig (int sig)
+{
+  caught_signal = true;
+}
+
+/* Export env variables declared by PAM modules.  */
+static void
+export_pamenv (void)
+{
+  char **env;
+
+  /* This is a copy but don't care to free as we exec later anyways.  */
+  env = pam_getenvlist (pamh);
+  while (env && *env)
+    {
+      if (putenv (*env) != 0)
+	xalloc_die ();
+      env++;
+    }
+}
+
+static void
+create_watching_parent (void)
+{
+  pid_t child;
+  sigset_t ourset;
+  int status;
+
+  retval = pam_open_session (pamh, 0);
+  if (retval != PAM_SUCCESS)
+    {
+      cleanup_pam (retval);
+      error (EXIT_FAILURE, 0, _("cannot not open session: %s"),
+	     pam_strerror (pamh, retval));
+    }
+  else
+    _pam_session_opened = 1;
+
+  child = fork ();
+  if (child == (pid_t) -1)
+    {
+      cleanup_pam (PAM_ABORT);
+      error (EXIT_FAILURE, errno, _("cannot create child process"));
+    }
+
+  /* the child proceeds to run the shell */
+  if (child == 0)
+    return;
+  
+  /* In the parent watch the child.  */
+
+  /* su without pam support does not have a helper that keeps
+     sitting on any directory so let's go to /.  */
+  if (chdir ("/") != 0)
+    error (0, errno, _("warning: cannot change directory to %s"), "/");
+
+  sigfillset (&ourset);
+  if (sigprocmask (SIG_BLOCK, &ourset, NULL))
+    {
+      error (0, errno, _("cannot block signals"));
+      caught_signal = true;
+    }
+  if (!caught_signal)
+    {
+      struct sigaction action;
+      action.sa_handler = su_catch_sig;
+      sigemptyset (&action.sa_mask);
+      action.sa_flags = 0;
+      sigemptyset (&ourset);
+      if (sigaddset (&ourset, SIGTERM)
+	  || sigaddset (&ourset, SIGALRM)
+	  || sigaction (SIGTERM, &action, NULL)
+	  || sigprocmask (SIG_UNBLOCK, &ourset, NULL))
+	{
+	  error (0, errno, _("cannot set signal handler"));
+	  caught_signal = true;
+	}
+    }
+  if (!caught_signal)
+    {
+      for (;;)
+	{
+	  pid_t pid;
+
+	  pid = waitpid (child, &status, WUNTRACED);
+
+	  if (WIFSTOPPED (status))
+	    {
+	      kill (getpid (), SIGSTOP);
+	      /* once we get here, we must have resumed */
+	      kill (pid, SIGCONT);
+	    }
+	  else
+	    break;
+	}
+      if (WIFSIGNALED (status))
+	status = WTERMSIG (status) + 128;
+      else
+	status = WEXITSTATUS (status);
+    }
+  else
+    status = 1;
+
+  if (caught_signal)
+    {
+      fprintf (stderr, _("\nSession terminated, killing shell..."));
+      kill (child, SIGTERM);
+    }
+
+  cleanup_pam (PAM_SUCCESS);
+
+  if (caught_signal)
+    {
+      sleep (2);
+      kill (child, SIGKILL);
+      fprintf (stderr, _(" ...killed.\n"));
+    }
+  exit (status);
+}
+#endif
+
 /* Ask the user for a password.
+   If PAM is in use, let PAM ask for the password if necessary.
    Return true if the user gives the correct password for entry PW,
    false if not.  Return true without asking for a password if run by UID 0
    or if PW has an empty password.  */
@@ -223,10 +401,52 @@ log_su (struct passwd const *pw, bool su
 static bool
 correct_password (const struct passwd *pw)
 {
+#ifdef USE_PAM
+  const struct passwd *lpw;
+  const char *cp;
+
+  retval = pam_start (simulate_login ? PAM_SERVICE_NAME_L : PAM_SERVICE_NAME,
+		      pw->pw_name, &conv, &pamh);
+  PAM_BAIL_P (return false);
+
+  if (isatty (0) && (cp = ttyname (0)) != NULL)
+    {
+      const char *tty;
+
+      if (strncmp (cp, "/dev/", 5) == 0)
+	tty = cp + 5;
+      else
+	tty = cp;
+      retval = pam_set_item (pamh, PAM_TTY, tty);
+      PAM_BAIL_P (return false);
+    }
+#if 0 /* Manpage discourages use of getlogin.  */
+  cp = getlogin ();
+  if (!(cp && *cp && (lpw = getpwnam (cp)) != NULL && lpw->pw_uid == getuid ()))
+#endif
+  lpw = getpwuid (getuid ());
+  if (lpw && lpw->pw_name)
+    {
+      retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
+      PAM_BAIL_P (return false);
+    }
+  retval = pam_authenticate (pamh, 0);
+  PAM_BAIL_P (return false);
+  retval = pam_acct_mgmt (pamh, 0);
+  if (retval == PAM_NEW_AUTHTOK_REQD)
+    {
+      /* Password has expired.  Offer option to change it.  */
+      retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
+      PAM_BAIL_P (return false);
+    }
+  PAM_BAIL_P (return false);
+  /* Must be authenticated if this point was reached.  */
+  return true;
+#else /* !USE_PAM */
   char *unencrypted, *encrypted, *correct;
 #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
-  struct spwd *sp = getspnam (pw->pw_name);
+  const struct spwd *sp = getspnam (pw->pw_name);
 
   endspent ();
   if (sp)
@@ -247,6 +467,7 @@ correct_password (const struct passwd *p
   encrypted = crypt (unencrypted, correct);
   memset (unencrypted, 0, strlen (unencrypted));
   return STREQ (encrypted, correct);
+#endif /* !USE_PAM */
 }
 
 /* Update `environ' for the new shell based on PW, with SHELL being
@@ -271,8 +492,8 @@ modify_environment (const struct passwd
       xsetenv ("USER", pw->pw_name);
       xsetenv ("LOGNAME", pw->pw_name);
       xsetenv ("PATH", (pw->pw_uid
-			? DEFAULT_LOGIN_PATH
-			: DEFAULT_ROOT_LOGIN_PATH));
+			? getdef_str ("PATH", DEFAULT_LOGIN_PATH)
+			: getdef_str ("SUPATH", DEFAULT_ROOT_LOGIN_PATH)));
     }
   else
     {
@@ -282,6 +503,12 @@ modify_environment (const struct passwd
 	{
 	  xsetenv ("HOME", pw->pw_dir);
 	  xsetenv ("SHELL", shell);
+	  if (getdef_bool ("ALWAYS_SET_PATH", 0))
+	    xsetenv ("PATH", (pw->pw_uid
+			      ? getdef_str ("PATH",
+					    DEFAULT_LOGIN_PATH)
+			      : getdef_str ("SUPATH",
+					    DEFAULT_ROOT_LOGIN_PATH)));
 	  if (pw->pw_uid)
 	    {
 	      xsetenv ("USER", pw->pw_name);
@@ -289,19 +516,41 @@ modify_environment (const struct passwd
 	    }
 	}
     }
+
+#ifdef USE_PAM
+  export_pamenv ();
+#endif
 }
 
 /* Become the user and group(s) specified by PW.  */
 
 static void
-change_identity (const struct passwd *pw)
+init_groups (const struct passwd *pw)
 {
 #ifdef HAVE_INITGROUPS
   errno = 0;
   if (initgroups (pw->pw_name, pw->pw_gid) == -1)
-    error (EXIT_FAILURE, errno, _("cannot set groups"));
+    {
+#ifdef USE_PAM
+      cleanup_pam (PAM_ABORT);
+#endif
+      error (EXIT_FAILURE, errno, _("cannot set groups"));
+    }
   endgrent ();
 #endif
+
+#ifdef USE_PAM
+  retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
+  if (retval != PAM_SUCCESS)
+    error (EXIT_FAILURE, 0, "%s", pam_strerror (pamh, retval));
+  else
+    _pam_cred_established = 1;
+#endif
+}
+
+static void
+change_identity (const struct passwd *pw)
+{
   if (setgid (pw->pw_gid))
     error (EXIT_FAILURE, errno, _("cannot set group id"));
   if (setuid (pw->pw_uid))
@@ -494,6 +743,7 @@ main (int argc, char **argv)
 #ifdef SYSLOG_FAILURE
       log_su (pw, false);
 #endif
+      sleep (getdef_num ("FAIL_DELAY", 1));
       error (EXIT_FAILURE, 0, _("incorrect password"));
     }
 #ifdef SYSLOG_SUCCESS
@@ -515,9 +765,21 @@ main (int argc, char **argv)
       shell = NULL;
     }
   shell = xstrdup (shell ? shell : pw->pw_shell);
-  modify_environment (pw, shell);
+
+  init_groups (pw);
+
+#ifdef USE_PAM
+  create_watching_parent ();
+  /* Now we're in the child.  */
+#endif
 
   change_identity (pw);
+
+  /* Set environment after pam_open_session, which may put KRB5CCNAME
+     into the pam_env, etc.  */
+
+  modify_environment (pw, shell);
+
   if (simulate_login && chdir (pw->pw_dir) != 0)
     error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
 
