| |
| |
| @@ -103,7 +103,7 @@ |
| # 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_LDADD = $(LDADD) $(LIB_CRYPT) @LIB_PAM@ |
| |
| dir_LDADD += $(LIB_ACL) |
| ls_LDADD += $(LIB_ACL) |
| |
| |
| @@ -38,6 +38,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.conf |
| + |
| +#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. |
| @@ -59,6 +69,15 @@ |
| prototype (returning `int') in <unistd.h>. */ |
| #define getusershell _getusershell_sys_proto_ |
| |
| +#ifdef USE_PAM |
| +# include <signal.h> |
| +# include <sys/wait.h> |
| +# include <sys/fsuid.h> |
| +# include <unistd.h> |
| +# include <security/pam_appl.h> |
| +# include <security/pam_misc.h> |
| +#endif /* USE_PAM */ |
| + |
| #include "system.h" |
| #include "getpass.h" |
| |
| @@ -128,15 +147,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; |
| |
| -static void run_shell (char const *, char const *, char **, size_t) |
| +static void run_shell (char const *, char const *, char **, size_t, |
| + const struct passwd *) |
| +#ifdef USE_PAM |
| + ; |
| +#else |
| ATTRIBUTE_NORETURN; |
| +#endif |
| |
| /* The name this program was run with. */ |
| char *program_name; |
| @@ -225,7 +251,26 @@ |
| } |
| #endif |
| |
| +#ifdef USE_PAM |
| +static pam_handle_t *pamh = NULL; |
| +static int retval; |
| +static struct pam_conv conv = { |
| + misc_conv, |
| + NULL |
| +}; |
| + |
| +#define PAM_BAIL_P if (retval) { \ |
| + pam_end(pamh, PAM_SUCCESS); \ |
| + return 0; \ |
| +} |
| +#define PAM_BAIL_P_VOID if (retval) { \ |
| + pam_end(pamh, PAM_SUCCESS); \ |
| +return; \ |
| +} |
| +#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. */ |
| @@ -233,6 +278,44 @@ |
| static bool |
| correct_password (const struct passwd *pw) |
| { |
| +#ifdef USE_PAM |
| + struct passwd *caller; |
| + char *tty_name, *ttyn; |
| + retval = pam_start(PROGRAM_NAME, pw->pw_name, &conv, &pamh); |
| + PAM_BAIL_P; |
| + |
| + if (getuid() != 0 && !isatty(0)) { |
| + fprintf(stderr, "standard in must be a tty\n"); |
| + exit(1); |
| + } |
| + |
| + caller = getpwuid(getuid()); |
| + if(caller != NULL && caller->pw_name != NULL) { |
| + retval = pam_set_item(pamh, PAM_RUSER, caller->pw_name); |
| + PAM_BAIL_P; |
| + } |
| + |
| + ttyn = ttyname(0); |
| + if (ttyn) { |
| + if (strncmp(ttyn, "/dev/", 5) == 0) |
| + tty_name = ttyn+5; |
| + else |
| + tty_name = ttyn; |
| + retval = pam_set_item(pamh, PAM_TTY, tty_name); |
| + PAM_BAIL_P; |
| + } |
| + retval = pam_authenticate(pamh, 0); |
| + PAM_BAIL_P; |
| + 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; |
| + } |
| + PAM_BAIL_P; |
| + /* must be authenticated if this point was reached */ |
| + return 1; |
| +#else /* !USE_PAM */ |
| char *unencrypted, *encrypted, *correct; |
| #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP |
| /* Shadow passwd stuff for SVR3 and maybe other systems. */ |
| @@ -257,6 +340,7 @@ |
| 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 |
| @@ -270,12 +354,18 @@ |
| /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. |
| Unset all other environment variables. */ |
| char const *term = getenv ("TERM"); |
| + char const *display = getenv ("DISPLAY"); |
| + char const *xauthority = getenv ("XAUTHORITY"); |
| if (term) |
| term = xstrdup (term); |
| environ = xmalloc ((6 + !!term) * sizeof (char *)); |
| environ[0] = NULL; |
| if (term) |
| xsetenv ("TERM", term); |
| + if (display) |
| + xsetenv ("DISPLAY", display); |
| + if (xauthority) |
| + xsetenv ("XAUTHORITY", xauthority); |
| xsetenv ("HOME", pw->pw_dir); |
| xsetenv ("SHELL", shell); |
| xsetenv ("USER", pw->pw_name); |
| @@ -308,8 +398,13 @@ |
| { |
| #ifdef HAVE_INITGROUPS |
| errno = 0; |
| - if (initgroups (pw->pw_name, pw->pw_gid) == -1) |
| + if (initgroups (pw->pw_name, pw->pw_gid) == -1) { |
| +#ifdef USE_PAM |
| + pam_close_session(pamh, 0); |
| + pam_end(pamh, PAM_ABORT); |
| +#endif |
| error (EXIT_FAILURE, errno, _("cannot set groups")); |
| + } |
| endgrent (); |
| #endif |
| if (setgid (pw->pw_gid)) |
| @@ -318,6 +413,31 @@ |
| error (EXIT_FAILURE, errno, _("cannot set user id")); |
| } |
| |
| +#ifdef USE_PAM |
| +static int caught=0; |
| +/* Signal handler for parent process later */ |
| +static void su_catch_sig(int sig) |
| +{ |
| + ++caught; |
| +} |
| + |
| +int |
| +pam_copyenv (pam_handle_t *pamh) |
| +{ |
| + char **env; |
| + |
| + env = pam_getenvlist(pamh); |
| + if(env) { |
| + while(*env) { |
| + if (putenv (*env)) |
| + xalloc_die (); |
| + env++; |
| + } |
| + } |
| + return(0); |
| +} |
| +#endif |
| + |
| /* Run SHELL, or DEFAULT_SHELL if SHELL is empty. |
| If COMMAND is nonzero, pass it to the shell with the -c option. |
| Pass ADDITIONAL_ARGS to the shell as more arguments; there |
| @@ -325,17 +445,49 @@ |
| |
| static void |
| run_shell (char const *shell, char const *command, char **additional_args, |
| - size_t n_additional_args) |
| + size_t n_additional_args, const struct passwd *pw) |
| { |
| size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1; |
| char const **args = xnmalloc (n_args, sizeof *args); |
| size_t argno = 1; |
| +#ifdef USE_PAM |
| + int child; |
| + sigset_t ourset; |
| + int status; |
| + |
| + retval = pam_open_session(pamh,0); |
| + if (retval != PAM_SUCCESS) { |
| + fprintf (stderr, "could not open session\n"); |
| + exit (1); |
| + } |
| + |
| +/* do this at the last possible moment, because environment variables may |
| + be passed even in the session phase |
| +*/ |
| + if(pam_copyenv(pamh) != PAM_SUCCESS) |
| + fprintf (stderr, "error copying PAM environment\n"); |
| + |
| + /* Credentials should be set in the parent */ |
| + if (pam_setcred(pamh, PAM_ESTABLISH_CRED) != PAM_SUCCESS) { |
| + pam_close_session(pamh, 0); |
| + fprintf(stderr, "could not set PAM credentials\n"); |
| + exit(1); |
| + } |
| + |
| + child = fork(); |
| + if (child == 0) { /* child shell */ |
| + change_identity (pw); |
| + pam_end(pamh, 0); |
| +#endif |
| |
| if (simulate_login) |
| { |
| char *arg0; |
| char *shell_basename; |
| |
| + if(chdir(pw->pw_dir)) |
| + error(0, errno, _("warning: cannot change directory to %s"), pw->pw_dir); |
| + |
| shell_basename = last_component (shell); |
| arg0 = xmalloc (strlen (shell_basename) + 2); |
| arg0[0] = '-'; |
| @@ -360,6 +512,66 @@ |
| error (0, errno, "%s", shell); |
| exit (exit_status); |
| } |
| +#ifdef USE_PAM |
| + } else if (child == -1) { |
| + fprintf(stderr, "can not fork user shell: %s", strerror(errno)); |
| + pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); |
| + pam_close_session(pamh, 0); |
| + pam_end(pamh, PAM_ABORT); |
| + exit(1); |
| + } |
| + /* parent only */ |
| + sigfillset(&ourset); |
| + if (sigprocmask(SIG_BLOCK, &ourset, NULL)) { |
| + fprintf(stderr, "%s: signal malfunction\n", PROGRAM_NAME); |
| + caught = 1; |
| + } |
| + if (!caught) { |
| + 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)) { |
| + fprintf(stderr, "%s: signal masking malfunction\n", PROGRAM_NAME); |
| + caught = 1; |
| + } |
| + } |
| + if (!caught) { |
| + do { |
| + int pid; |
| + |
| + pid = waitpid(-1, &status, WUNTRACED); |
| + |
| + if (WIFSTOPPED(status)) { |
| + kill(getpid(), SIGSTOP); |
| + /* once we get here, we must have resumed */ |
| + kill(pid, SIGCONT); |
| + } |
| + } while (WIFSTOPPED(status)); |
| + } |
| + |
| + if (caught) { |
| + fprintf(stderr, "\nSession terminated, killing shell..."); |
| + kill (child, SIGTERM); |
| + } |
| + /* Not checking retval on this because we need to call close session */ |
| + pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); |
| + retval = pam_close_session(pamh, 0); |
| + PAM_BAIL_P_VOID; |
| + retval = pam_end(pamh, PAM_SUCCESS); |
| + PAM_BAIL_P_VOID; |
| + if (caught) { |
| + sleep(2); |
| + kill(child, SIGKILL); |
| + fprintf(stderr, " ...killed.\n"); |
| + exit(-1); |
| + } |
| + exit (WEXITSTATUS(status)); |
| +#endif /* USE_PAM */ |
| } |
| |
| /* Return true if SHELL is a restricted shell (one not returned by |
| @@ -527,9 +739,9 @@ |
| shell = xstrdup (shell ? shell : pw->pw_shell); |
| modify_environment (pw, shell); |
| |
| +#ifndef USE_PAM |
| change_identity (pw); |
| - if (simulate_login && chdir (pw->pw_dir) != 0) |
| - error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir); |
| +#endif |
| |
| - run_shell (shell, command, argv + optind, MAX (0, argc - optind)); |
| + run_shell (shell, command, argv + optind, MAX (0, argc - optind), pw); |
| } |
| |
| |
| @@ -13395,8 +13395,11 @@ |
| @findex syslog |
| @command{su} can optionally be compiled to use @code{syslog} to report |
| failed, and optionally successful, @command{su} attempts. (If the system |
| -supports @code{syslog}.) However, GNU @command{su} does not check if the |
| -user is a member of the @code{wheel} group; see below. |
| +supports @code{syslog}.) |
| + |
| +This version of @command{su} has support for using PAM for |
| +authentication. You can edit @file{/etc/pam.d/su} to customize its |
| +behaviour. |
| |
| The program accepts the following options. Also see @ref{Common options}. |
| |
| @@ -12815,6 +12815,8 @@ |
| @env{PATH} to a compiled-in default value. Change to @var{user}'s home |
| directory. Prepend @samp{-} to the shell's name, intended to make it |
| read its login startup file(s). |
| +Additionaly @env{DISPLAY} and @env{XAUTHORITY} environment variables |
| +are preserved as well for PAM functionality. |
| |
| @item -m |
| @itemx -p |
| @@ -13477,33 +13480,6 @@ |
| the exit status of the subshell otherwise |
| @end display |
| |
| -@cindex wheel group, not supported |
| -@cindex group wheel, not supported |
| -@cindex fascism |
| -@subsection Why GNU @command{su} does not support the @samp{wheel} group |
| - |
| -(This section is by Richard Stallman.) |
| - |
| -@cindex Twenex |
| -@cindex MIT AI lab |
| -Sometimes a few of the users try to hold total power over all the |
| -rest. For example, in 1984, a few users at the MIT AI lab decided to |
| -seize power by changing the operator password on the Twenex system and |
| -keeping it secret from everyone else. (I was able to thwart this coup |
| -and give power back to the users by patching the kernel, but I |
| -wouldn't know how to do that in Unix.) |
| - |
| -However, occasionally the rulers do tell someone. Under the usual |
| -@command{su} mechanism, once someone learns the root password who |
| -sympathizes with the ordinary users, he or she can tell the rest. The |
| -``wheel group'' feature would make this impossible, and thus cement the |
| -power of the rulers. |
| - |
| -I'm on the side of the masses, not that of the rulers. If you are |
| -used to supporting the bosses and sysadmins in whatever they do, you |
| -might find this idea strange at first. |
| - |
| - |
| @node Process control |
| @chapter Process control |
| |
| |
| |
| @@ -44,6 +44,13 @@ |
| gl_INIT |
| coreutils_MACROS |
| |
| +dnl Give the chance to enable PAM |
| +AC_ARG_ENABLE(pam, dnl |
| +[ --enable-pam Enable use of the PAM libraries], |
| +[AC_DEFINE(USE_PAM, 1, [Define if you want to use PAM]) |
| +LIB_PAM="-ldl -lpam -lpam_misc" |
| +AC_SUBST(LIB_PAM)]) |
| + |
| AC_FUNC_FORK |
| |
| optional_bin_progs= |