Add pam support in su Index: Makefile.in =================================================================== --- Makefile.in.orig 2010-10-15 16:31:46.000000000 +0200 +++ Makefile.in 2010-11-11 16:02:50.366117868 +0100 @@ -991,6 +991,7 @@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERL = @PERL@ POSIX_SHELL = @POSIX_SHELL@ Index: configure =================================================================== --- configure.orig 2010-11-11 16:02:50.342113626 +0100 +++ configure 2010-11-11 16:04:17.257475264 +0100 @@ -639,6 +639,7 @@ OPTIONAL_BIN_PROGS INSTALL_SU LIB_GMP LIB_CRYPT +PAM_LIBS GNULIB_TEST_WARN_CFLAGS GNULIB_WARN_CFLAGS WERROR_CFLAGS @@ -1551,6 +1552,7 @@ enable_xattr enable_libcap with_tty_group enable_gcc_warnings +enable_pam with_gmp enable_install_program enable_no_install_program @@ -2203,6 +2205,7 @@ Optional Features: --disable-xattr do not support extended attributes --disable-libcap disable libcap support --enable-gcc-warnings turn on lots of GCC warnings (for developers) + --disable-pam Disable PAM support in su (default=auto) --enable-install-program=PROG_LIST install the programs in PROG_LIST (comma-separated, default: none) @@ -53157,6 +53160,111 @@ $as_echo "#define HAVE_WORKING_FORK 1" > fi +# Check whether --enable-pam was given. +if test "${enable_pam+set}" = set; then + enableval=$enable_pam; +else + enable_pam=yes +fi + +if test "x$enable_pam" != xno; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5 +$as_echo_n "checking for pam_start in -lpam... " >&6; } +if test "${ac_cv_lib_pam_pam_start+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpam $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pam_start (); +int +main () +{ +return pam_start (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then + ac_cv_lib_pam_pam_start=yes +else + ac_cv_lib_pam_pam_start=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pam_pam_start" >&5 +$as_echo "$ac_cv_lib_pam_pam_start" >&6; } +if test "x$ac_cv_lib_pam_pam_start" = x""yes; then + enable_pam=yes +else + enable_pam=no +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for misc_conv in -lpam_misc" >&5 +$as_echo_n "checking for misc_conv in -lpam_misc... " >&6; } +if test "${ac_cv_lib_pam_misc_misc_conv+set}" = set; then + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpam_misc $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char misc_conv (); +int +main () +{ +return misc_conv (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then + ac_cv_lib_pam_misc_misc_conv=yes +else + ac_cv_lib_pam_misc_misc_conv=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pam_misc_misc_conv" >&5 +$as_echo "$ac_cv_lib_pam_misc_misc_conv" >&6; } +if test "x$ac_cv_lib_pam_misc_misc_conv" = x""yes; then + : +else + enable_pam=no +fi + + if test "x$enable_pam" != xno; then + +$as_echo "#define USE_PAM 1" >>confdefs.h + + PAM_LIBS="-lpam -lpam_misc" + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable PAM support in su" >&5 +$as_echo_n "checking whether to enable PAM support in su... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_pam" >&5 +$as_echo "$enable_pam" >&6; } + optional_bin_progs= for ac_func in chroot do : Index: configure.ac =================================================================== --- configure.ac.orig 2010-10-13 10:58:27.000000000 +0200 +++ configure.ac 2010-11-11 16:02:50.442131303 +0100 @@ -135,6 +135,20 @@ fi 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([chroot], gl_ADD_PROG([optional_bin_progs], [chroot])) Index: doc/Makefile.in =================================================================== --- doc/Makefile.in.orig 2010-10-15 16:31:44.000000000 +0200 +++ doc/Makefile.in 2010-11-11 16:02:50.442131303 +0100 @@ -987,6 +987,7 @@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERL = @PERL@ POSIX_SHELL = @POSIX_SHELL@ Index: gnulib-tests/Makefile.in =================================================================== --- gnulib-tests/Makefile.in.orig 2010-10-15 16:32:45.000000000 +0200 +++ gnulib-tests/Makefile.in 2010-11-11 16:02:50.490139787 +0100 @@ -2378,6 +2378,7 @@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERL = @PERL@ POSIX_SHELL = @POSIX_SHELL@ Index: lib/Makefile.in =================================================================== --- lib/Makefile.in.orig 2010-10-15 16:31:45.000000000 +0200 +++ lib/Makefile.in 2010-11-11 16:02:50.550150395 +0100 @@ -1073,6 +1073,7 @@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERL = @PERL@ POSIX_SHELL = @POSIX_SHELL@ Index: man/Makefile.in =================================================================== --- man/Makefile.in.orig 2010-11-11 16:02:50.294105140 +0100 +++ man/Makefile.in 2010-11-11 16:02:50.554151102 +0100 @@ -956,6 +956,7 @@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERL = @PERL@ POSIX_SHELL = @POSIX_SHELL@ Index: src/Makefile.am =================================================================== --- src/Makefile.am.orig 2010-10-12 13:13:16.000000000 +0200 +++ src/Makefile.am 2010-11-11 16:02:50.594158172 +0100 @@ -352,7 +352,8 @@ factor_LDADD += $(LIB_GMP) uptime_LDADD += $(GETLOADAVG_LIBS) # for crypt -su_LDADD += $(LIB_CRYPT) +su_SOURCES = su.c getdef.c +su_LDADD = $(LDADD) $(LIB_CRYPT) $(PAM_LIBS) # for various ACL functions copy_LDADD += $(LIB_ACL) Index: src/Makefile.in =================================================================== --- src/Makefile.in.orig 2010-10-15 17:06:15.000000000 +0200 +++ src/Makefile.in 2010-11-11 16:09:48.436006623 +0100 @@ -553,9 +553,10 @@ stdbuf_DEPENDENCIES = $(am__DEPENDENCIES stty_SOURCES = stty.c stty_OBJECTS = stty.$(OBJEXT) stty_DEPENDENCIES = $(am__DEPENDENCIES_2) -su_SOURCES = su.c -su_OBJECTS = su.$(OBJEXT) -su_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) +am_su_OBJECTS = su.$(OBJEXT) getdef.$(OBJEXT) +su_OBJECTS = $(am_su_OBJECTS) +su_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) sum_SOURCES = sum.c sum_OBJECTS = sum.$(OBJEXT) sum_DEPENDENCIES = $(am__DEPENDENCIES_2) @@ -663,9 +664,9 @@ SOURCES = $(nodist_libver_a_SOURCES) $(_ $(rmdir_SOURCES) runcon.c seq.c setuidgid.c $(sha1sum_SOURCES) \ $(sha224sum_SOURCES) $(sha256sum_SOURCES) $(sha384sum_SOURCES) \ $(sha512sum_SOURCES) shred.c shuf.c sleep.c sort.c split.c \ - $(stat_SOURCES) stdbuf.c stty.c su.c sum.c sync.c tac.c tail.c \ - tee.c test.c $(timeout_SOURCES) touch.c tr.c true.c truncate.c \ - tsort.c tty.c $(uname_SOURCES) unexpand.c uniq.c unlink.c \ + $(stat_SOURCES) stdbuf.c stty.c $(su_SOURCES) sum.c sync.c tac.c \ + tail.c tee.c test.c $(timeout_SOURCES) touch.c tr.c true.c \ + truncate.c tsort.c tty.c $(uname_SOURCES) unexpand.c uniq.c unlink.c \ uptime.c users.c $(vdir_SOURCES) wc.c who.c whoami.c yes.c DIST_SOURCES = $(__SOURCES) $(arch_SOURCES) base64.c basename.c cat.c \ chcon.c $(chgrp_SOURCES) chmod.c $(chown_SOURCES) chroot.c \ @@ -682,7 +683,7 @@ DIST_SOURCES = $(__SOURCES) $(arch_SOURC setuidgid.c $(sha1sum_SOURCES) $(sha224sum_SOURCES) \ $(sha256sum_SOURCES) $(sha384sum_SOURCES) $(sha512sum_SOURCES) \ shred.c shuf.c sleep.c sort.c split.c $(stat_SOURCES) stdbuf.c \ - stty.c su.c sum.c sync.c tac.c tail.c tee.c test.c \ + stty.c $(su_SOURCES) sum.c sync.c tac.c tail.c tee.c test.c \ $(timeout_SOURCES) touch.c tr.c true.c truncate.c tsort.c \ tty.c $(uname_SOURCES) unexpand.c uniq.c unlink.c uptime.c \ users.c $(vdir_SOURCES) wc.c who.c whoami.c yes.c @@ -1363,6 +1364,7 @@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERL = @PERL@ POSIX_SHELL = @POSIX_SHELL@ @@ -1779,7 +1781,8 @@ stdbuf_LDADD = $(LDADD) $(LIBICONV) stty_LDADD = $(LDADD) # for crypt -su_LDADD = $(LDADD) $(LIB_CRYPT) +su_SOURCES = su.c getdef.c +su_LDADD = $(LDADD) $(LIB_CRYPT) $(PAM_LIBS) sum_LDADD = $(LDADD) sync_LDADD = $(LDADD) tac_LDADD = $(LDADD) @@ -2425,6 +2428,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/find-mount-point.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fmt.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fold.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getdef.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getlimits.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ginstall-copy.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ginstall-cp-hash.Po@am__quote@ Index: src/getdef.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ src/getdef.c 2010-11-11 16:02:50.662170193 +0100 @@ -0,0 +1,259 @@ +/* Copyright (C) 2003, 2004, 2005 Thorsten Kukuk + Author: Thorsten Kukuk + + 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 +#endif + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#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-11-11 16:02:50.678173021 +0100 @@ -0,0 +1,29 @@ +/* Copyright (C) 2003, 2005 Thorsten Kukuk + Author: Thorsten Kukuk + + 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 2010-10-11 19:35:11.000000000 +0200 +++ src/su.c 2010-11-11 16:02:50.694175850 +0100 @@ -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,12 +62,22 @@ #include #include #include +#ifdef USE_PAM +#include +#include +#include +#include +#include +#endif #include "system.h" #include "getpass.h" #if HAVE_SYSLOG_H && HAVE_SYSLOG # include +# define SYSLOG_SUCCESS 1 +# define SYSLOG_FAILURE 1 +# define SYSLOG_NON_ROOT 1 #else # undef SYSLOG_SUCCESS # undef SYSLOG_FAILURE @@ -91,19 +111,13 @@ # include #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" @@ -111,8 +125,9 @@ /* The user to become if none is specified. */ #define DEFAULT_USER "root" +#ifndef USE_PAM char *crypt (char const *key, char const *salt); - +#endif static void run_shell (char const *, char const *, char **, size_t) ATTRIBUTE_NORETURN; @@ -125,6 +140,13 @@ static bool simulate_login; /* If true, change some environment vars to indicate the user su'd to. */ static bool change_environment; +#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 struct option const longopts[] = { {"command", required_argument, NULL, 'c'}, @@ -200,7 +222,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. */ @@ -208,10 +385,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) @@ -232,6 +451,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 @@ -256,8 +476,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 { @@ -267,6 +487,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); @@ -274,19 +500,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_CANCELED, 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_CANCELED, errno, _("cannot set group id")); if (setuid (pw->pw_uid)) @@ -479,6 +727,7 @@ main (int argc, char **argv) #ifdef SYSLOG_FAILURE log_su (pw, false); #endif + sleep (getdef_num ("FAIL_DELAY", 1)); error (EXIT_CANCELED, 0, _("incorrect password")); } #ifdef SYSLOG_SUCCESS @@ -500,9 +749,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); Index: tests/Makefile.in =================================================================== --- tests/Makefile.in.orig 2010-10-15 16:31:45.000000000 +0200 +++ tests/Makefile.in 2010-11-11 16:02:50.750185750 +0100 @@ -1045,6 +1045,7 @@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ +PAM_LIBS = @PAM_LIBS@ PATH_SEPARATOR = @PATH_SEPARATOR@ PERL = @PERL@ POSIX_SHELL = @POSIX_SHELL@