3
0
forked from pool/coreutils
coreutils/coreutils-su.patch

1665 lines
45 KiB
Diff
Raw Normal View History

From: Bernhard Voelker <mail@bernhard-voelker.de>
Date: Tue, 19 Mar 2013 17:00:00 +0200
Subject: add su again for compatibility
As su(1) has been moved from coreutils to util-linux upstreams, this package
must provide it via compatibility symlinks to the old coreutils version.
This is needed until there is a newer version of the receiving util-linux
package available which would then provide su.
---
doc/coreutils.texi | 141 +++++++
man/local.mk | 2
man/su.x | 4
po/coreutils.pot | 67 +++
src/cu-progs.mk | 1
src/getdef.c | 259 +++++++++++++
src/getdef.h | 29 +
src/local.mk | 6
src/su.c | 945 ++++++++++++++++++++++++++++++++++++++++++++++++++
tests/local.mk | 1
tests/misc/su-fail.sh | 30 +
11 files changed, 1483 insertions(+), 2 deletions(-)
Index: src/cu-progs.mk
===================================================================
--- src/cu-progs.mk.orig
+++ src/cu-progs.mk
@@ -87,6 +87,7 @@ default__progs += src/sleep
default__progs += src/sort
default__progs += src/split
default__progs += src/stat
+default__progs += src/su
default__progs += src/sum
default__progs += src/sync
default__progs += src/tac
Index: src/local.mk
===================================================================
--- src/local.mk.orig
+++ src/local.mk
@@ -205,6 +205,12 @@ src_who_LDADD = $(LDADD)
src_whoami_LDADD = $(LDADD)
src_yes_LDADD = $(LDADD)
+
+src_su_CFLAGS = -fpie -DUSE_PAM=1
+src_su_LDFLAGS = -pie
+PAM_LIBS = -lpam -lpam_misc
+src_su_LDADD = src/getdef.c $(LDADD) -lcrypt $(PAM_LIBS)
+
# Synonyms. Recall that Automake transliterates '[' and '/' to '_'.
src___LDADD = $(src_test_LDADD)
src_dir_LDADD = $(src_ls_LDADD)
Index: src/su.c
===================================================================
--- /dev/null
+++ src/su.c
@@ -0,0 +1,945 @@
+/* su for GNU. Run a shell with substitute user and group IDs.
+ Copyright (C) 1992-2012 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <http://www.gnu.org/licenses/>. */
+
+/* Run a shell with the real and effective UID and GID and groups
+ of USER, default 'root'.
+
+ The shell run is taken from USER's password entry, /bin/sh if
+ none is specified there. If the account has a password, su
+ prompts for a password unless run by a user with real UID 0.
+
+ Does not change the current directory.
+ Sets 'HOME' and 'SHELL' from the password entry for USER, and if
+ USER is not root, sets 'USER' and 'LOGNAME' to USER.
+ The subshell is not a login shell.
+
+ If one or more ARGs are given, they are passed as additional
+ arguments to the subshell.
+
+ Does not handle /bin/sh or other shells specially
+ (setting argv[0] to "-su", passing -c only to certain shells, etc.).
+ I don't see the point in doing that, and it's ugly.
+
+ This program intentionally does not support a "wheel group" that
+ 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.
+
+ -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0).
+ Never logs attempted su's to nonexistent accounts.
+
+ Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
+
+#include <config.h>
+#include <stdio.h>
+#include <getopt.h>
+#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
+
+#include "system.h"
+#include "getpass.h"
+
+#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
+# undef SYSLOG_NON_ROOT
+#endif
+
+#if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#ifndef HAVE_ENDGRENT
+# define endgrent() ((void) 0)
+#endif
+
+#ifndef HAVE_ENDPWENT
+# define endpwent() ((void) 0)
+#endif
+
+#if HAVE_SHADOW_H
+# include <shadow.h>
+#endif
+
+#include "error.h"
+
+/* The official name of this program (e.g., no 'g' prefix). */
+#define PROGRAM_NAME "su"
+
+#define AUTHORS proper_name ("David MacKenzie")
+
+#if HAVE_PATHS_H
+# include <paths.h>
+#endif
+
+#include "getdef.h"
+
+/* The default PATH for simulated logins to non-superuser accounts. */
+#define DEFAULT_LOGIN_PATH "/usr/local/bin:/bin:/usr/bin"
+
+/* The default PATH for simulated logins to superuser accounts. */
+#define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin"
+
+/* The shell to run if none is given in the user's passwd entry. */
+#define DEFAULT_SHELL "/bin/sh"
+
+/* 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;
+
+/* If true, pass the '-f' option to the subshell. */
+static bool fast_startup;
+
+/* If true, simulate a login instead of just starting a shell. */
+static bool simulate_login;
+
+/* If true, change some environment vars to indicate the user su'd to. */
+static bool change_environment;
+
+/* If true, then don't call setsid() with a command. */
+int same_session = 0;
+
+#ifdef USE_PAM
+static bool _pam_session_opened;
+static bool _pam_cred_established;
+#endif
+
+static struct option const longopts[] =
+{
+ {"command", required_argument, NULL, 'c'},
+ {"session-command", required_argument, NULL, 'C'},
+ {"fast", no_argument, NULL, 'f'},
+ {"login", no_argument, NULL, 'l'},
+ {"preserve-environment", no_argument, NULL, 'p'},
+ {"shell", required_argument, NULL, 's'},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ {NULL, 0, NULL, 0}
+};
+
+/* Add NAME=VAL to the environment, checking for out of memory errors. */
+
+static void
+xsetenv (char const *name, char const *val)
+{
+ size_t namelen = strlen (name);
+ size_t vallen = strlen (val);
+ char *string = xmalloc (namelen + 1 + vallen + 1);
+ strcpy (string, name);
+ string[namelen] = '=';
+ strcpy (string + namelen + 1, val);
+ if (putenv (string) != 0)
+ xalloc_die ();
+}
+
+#if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
+/* Log the fact that someone has run su to the user given by PW;
+ if SUCCESSFUL is true, they gave the correct password, etc. */
+
+static void
+log_su (struct passwd const *pw, bool successful)
+{
+ const char *new_user, *old_user, *tty;
+
+# ifndef SYSLOG_NON_ROOT
+ if (pw->pw_uid)
+ return;
+# endif
+ new_user = pw->pw_name;
+ /* The utmp entry (via getlogin) is probably the best way to identify
+ the user, especially if someone su's from a su-shell. */
+ old_user = getlogin ();
+ if (!old_user)
+ {
+ /* getlogin can fail -- usually due to lack of utmp entry.
+ Resort to getpwuid. */
+ errno = 0;
+ uid_t ruid = getuid ();
+ uid_t NO_UID = -1;
+ struct passwd *pwd = (ruid == NO_UID && errno ? NULL : getpwuid (ruid));
+ old_user = (pwd ? pwd->pw_name : "");
+ }
+ tty = ttyname (STDERR_FILENO);
+ if (!tty)
+ tty = "none";
+ /* 4.2BSD openlog doesn't have the third parameter. */
+ openlog (last_component (program_name), 0
+# ifdef LOG_AUTH
+ , LOG_AUTH
+# endif
+ );
+ syslog (LOG_NOTICE,
+# ifdef SYSLOG_NON_ROOT
+ "%s(to %s) %s on %s",
+# else
+ "%s%s on %s",
+# endif
+ successful ? "" : "FAILED SU ",
+# ifdef SYSLOG_NON_ROOT
+ new_user,
+# endif
+ old_user, tty);
+ closelog ();
+}
+#endif
+
+#ifdef USE_PAM
+#define PAM_SERVICE_NAME PROGRAM_NAME
+#define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
+static sig_atomic_t volatile 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 = 0;
+
+ 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 (!same_session)
+ {
+ if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT))
+ {
+ error (0, errno, _("cannot set signal handler"));
+ caught_signal = true;
+ }
+ }
+ if (!caught_signal && (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 && !same_session && (sigaction(SIGINT, &action, NULL)
+ || sigaction(SIGQUIT, &action, NULL)))
+ {
+ error (0, errno, _("cannot set signal handler"));
+ caught_signal = true;
+ }
+ }
+ if (!caught_signal)
+ {
+ pid_t pid;
+ for (;;)
+ {
+ pid = waitpid (child, &status, WUNTRACED);
+
+ if (pid != (pid_t)-1 && WIFSTOPPED (status))
+ {
+ kill (getpid (), SIGSTOP);
+ /* once we get here, we must have resumed */
+ kill (pid, SIGCONT);
+ }
+ else
+ break;
+ }
+ if (pid != (pid_t)-1)
+ if (WIFSIGNALED (status))
+ status = WTERMSIG (status) + 128;
+ else
+ status = WEXITSTATUS (status);
+ else
+ status = 1;
+ }
+ 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. */
+
+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. */
+ const struct spwd *sp = getspnam (pw->pw_name);
+
+ endspent ();
+ if (sp)
+ correct = sp->sp_pwdp;
+ else
+#endif
+ correct = pw->pw_passwd;
+
+ if (getuid () == 0 || !correct || correct[0] == '\0')
+ return true;
+
+ unencrypted = getpass (_("Password:"));
+ if (!unencrypted)
+ {
+ error (0, 0, _("getpass: cannot open /dev/tty"));
+ return false;
+ }
+ encrypted = crypt (unencrypted, correct);
+ memset (unencrypted, 0, strlen (unencrypted));
+ return STREQ (encrypted, correct);
+#endif /* !USE_PAM */
+}
+
+/* Add or clear /sbin and /usr/sbin for the su command
+ used without `-'. */
+
+/* Set if /sbin is found in path. */
+#define SBIN_MASK 0x01
+/* Set if /usr/sbin is found in path. */
+#define USBIN_MASK 0x02
+
+static char *
+addsbin (const char *const path)
+{
+ unsigned char smask = 0;
+ char *ptr, *tmp, *cur, *ret = NULL;
+ size_t len;
+
+ if (!path || *path == 0)
+ return NULL;
+
+ tmp = xstrdup (path);
+ cur = tmp;
+ for (ptr = strsep (&cur, ":"); ptr != NULL; ptr = strsep (&cur, ":"))
+ {
+ if (!strcmp (ptr, "/sbin"))
+ smask |= SBIN_MASK;
+ if (!strcmp (ptr, "/usr/sbin"))
+ smask |= USBIN_MASK;
+ }
+
+ if ((smask & (USBIN_MASK|SBIN_MASK)) == (USBIN_MASK|SBIN_MASK))
+ {
+ free (tmp);
+ return NULL;
+ }
+
+ len = strlen (path);
+ if (!(smask & USBIN_MASK))
+ len += strlen ("/usr/sbin:");
+
+ if (!(smask & SBIN_MASK))
+ len += strlen (":/sbin");
+
+ ret = xmalloc (len + 1);
+ strcpy (tmp, path);
+
+ *ret = 0;
+ cur = tmp;
+ for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":"))
+ {
+ if (!strcmp (ptr, "."))
+ continue;
+ if (*ret)
+ strcat (ret, ":");
+ if (!(smask & USBIN_MASK) && !strcmp (ptr, "/bin"))
+ {
+ strcat (ret, "/usr/sbin:");
+ strcat (ret, ptr);
+ smask |= USBIN_MASK;
+ continue;
+ }
+ if (!(smask & SBIN_MASK) && !strcmp (ptr, "/usr/bin"))
+ {
+ strcat (ret, ptr);
+ strcat (ret, ":/sbin");
+ smask |= SBIN_MASK;
+ continue;
+ }
+ strcat (ret, ptr);
+ }
+ free (tmp);
+
+ if (!(smask & USBIN_MASK))
+ strcat (ret, ":/usr/sbin");
+
+ if (!(smask & SBIN_MASK))
+ strcat (ret, ":/sbin");
+
+ return ret;
+}
+
+static char *
+clearsbin (const char *const path)
+{
+ char *ptr, *tmp, *cur, *ret = NULL;
+
+ if (!path || *path == 0)
+ return NULL;
+
+ tmp = strdup (path);
+ if (!tmp)
+ return NULL;
+
+ ret = xmalloc (strlen (path) + 1);
+ *ret = 0;
+ cur = tmp;
+ for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":"))
+ {
+ if (!strcmp (ptr, "/sbin"))
+ continue;
+ if (!strcmp (ptr, "/usr/sbin"))
+ continue;
+ if (!strcmp (ptr, "/usr/local/sbin"))
+ continue;
+ if (*ret)
+ strcat (ret, ":");
+ strcat (ret, ptr);
+ }
+ free (tmp);
+
+ return ret;
+}
+
+/* Update 'environ' for the new shell based on PW, with SHELL being
+ the value for the SHELL environment variable. */
+
+static void
+modify_environment (const struct passwd *pw, const char *shell)
+{
+ if (simulate_login)
+ {
+ /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
+ Unset all other environment variables. */
+ char const *term = getenv ("TERM");
+ if (term)
+ term = xstrdup (term);
+ environ = xmalloc ((6 + !!term) * sizeof (char *));
+ environ[0] = NULL;
+ if (term)
+ xsetenv ("TERM", term);
+ xsetenv ("HOME", pw->pw_dir);
+ xsetenv ("SHELL", shell);
+ xsetenv ("USER", pw->pw_name);
+ xsetenv ("LOGNAME", pw->pw_name);
+ xsetenv ("PATH", (pw->pw_uid
+ ? getdef_str ("PATH", DEFAULT_LOGIN_PATH)
+ : getdef_str ("SUPATH", DEFAULT_ROOT_LOGIN_PATH)));
+ }
+ else
+ {
+ /* Set HOME, SHELL, and if not becoming a super-user,
+ USER and LOGNAME. */
+ if (change_environment)
+ {
+ 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)));
+ else
+ {
+ char const *path = getenv ("PATH");
+ char *new = NULL;
+
+ if (pw->pw_uid)
+ new = clearsbin (path);
+ else
+ new = addsbin (path);
+
+ if (new)
+ {
+ xsetenv ("PATH", new);
+ free (new);
+ }
+ }
+ if (pw->pw_uid)
+ {
+ xsetenv ("USER", pw->pw_name);
+ xsetenv ("LOGNAME", pw->pw_name);
+ }
+ }
+ }
+
+#ifdef USE_PAM
+ export_pamenv ();
+#endif
+}
+
+/* Become the user and group(s) specified by PW. */
+
+static void
+init_groups (const struct passwd *pw)
+{
+#ifdef HAVE_INITGROUPS
+ errno = 0;
+ if (initgroups (pw->pw_name, pw->pw_gid) == -1)
+ {
+#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))
+ error (EXIT_CANCELED, errno, _("cannot set user id"));
+}
+
+/* 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
+ are N_ADDITIONAL_ARGS extra arguments. */
+
+static void
+run_shell (char const *shell, char const *command, char **additional_args,
+ size_t n_additional_args)
+{
+ 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;
+
+ if (simulate_login)
+ {
+ char *arg0;
+ char *shell_basename;
+
+ shell_basename = last_component (shell);
+ arg0 = xmalloc (strlen (shell_basename) + 2);
+ arg0[0] = '-';
+ strcpy (arg0 + 1, shell_basename);
+ args[0] = arg0;
+ }
+ else
+ args[0] = last_component (shell);
+ if (fast_startup)
+ args[argno++] = "-f";
+ if (command)
+ {
+ args[argno++] = "-c";
+ args[argno++] = command;
+ }
+ memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
+ args[argno + n_additional_args] = NULL;
+ execv (shell, (char **) args);
+
+ {
+ int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
+ error (0, errno, "%s", shell);
+ exit (exit_status);
+ }
+}
+
+/* Return true if SHELL is a restricted shell (one not returned by
+ getusershell), else false, meaning it is a standard shell. */
+
+static bool
+restricted_shell (const char *shell)
+{
+ char *line;
+
+ setusershell ();
+ while ((line = getusershell ()) != NULL)
+ {
+ if (*line != '#' && STREQ (line, shell))
+ {
+ endusershell ();
+ return false;
+ }
+ }
+ endusershell ();
+ return true;
+}
+
+void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ emit_try_help ();
+ else
+ {
+ printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
+ fputs (_("\
+Change the effective user id and group id to that of USER.\n\
+\n\
+ -, -l, --login make the shell a login shell\n\
+ -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
+ --session-command=COMMAND pass a single COMMAND to the shell with -c\n\
+ and do not create a new session\n\
+ -f, --fast pass -f to the shell (for csh or tcsh)\n\
+ -m, --preserve-environment do not reset environment variables\n\
+ -p same as -m\n\
+ -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ fputs (_("\
+\n\
+A mere - implies -l. If USER not given, assume root.\n\
+"), stdout);
+ emit_ancillary_info ();
+ }
+ exit (status);
+}
+
+int
+main (int argc, char **argv)
+{
+ int optc;
+ const char *new_user = DEFAULT_USER;
+ char *command = NULL;
+ int request_same_session = 0;
+ char *shell = NULL;
+ struct passwd *pw;
+ struct passwd pw_copy;
+
+ initialize_main (&argc, &argv);
+ set_program_name (argv[0]);
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ initialize_exit_failure (EXIT_CANCELED);
+ atexit (close_stdout);
+
+ fast_startup = false;
+ simulate_login = false;
+ change_environment = true;
+
+ while ((optc = getopt_long (argc, argv, "c:C:flmps:", longopts, NULL)) != -1)
+ {
+ switch (optc)
+ {
+ case 'c':
+ command = optarg;
+ if (NULL != getenv ("SU_COMMAND_SAME_SESSION") ||
+ NULL != getenv ("SU_COMMAND_OPENS_SESSION"))
+ request_same_session = 1;
+ break;
+
+ case 'C':
+ command = optarg;
+ request_same_session = 1;
+ break;
+
+ case 'f':
+ fast_startup = true;
+ break;
+
+ case 'l':
+ simulate_login = true;
+ break;
+
+ case 'm':
+ case 'p':
+ change_environment = false;
+ break;
+
+ case 's':
+ shell = optarg;
+ break;
+
+ case_GETOPT_HELP_CHAR;
+
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+ default:
+ usage (EXIT_CANCELED);
+ }
+ }
+
+ if (optind < argc && STREQ (argv[optind], "-"))
+ {
+ simulate_login = true;
+ ++optind;
+ }
+ if (optind < argc)
+ new_user = argv[optind++];
+
+ pw = getpwnam (new_user);
+ if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
+ && pw->pw_passwd))
+ error (EXIT_CANCELED, 0, _("user %s does not exist"), new_user);
+
+ /* Make a copy of the password information and point pw at the local
+ copy instead. Otherwise, some systems (e.g. GNU/Linux) would clobber
+ the static data through the getlogin call from log_su.
+ Also, make sure pw->pw_shell is a nonempty string.
+ It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
+ but that doesn't have a default shell listed. */
+ pw_copy = *pw;
+ pw = &pw_copy;
+ pw->pw_name = xstrdup (pw->pw_name);
+ pw->pw_passwd = xstrdup (pw->pw_passwd);
+ pw->pw_dir = xstrdup (pw->pw_dir);
+ pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
+ ? pw->pw_shell
+ : DEFAULT_SHELL);
+ endpwent ();
+
+ if (!correct_password (pw))
+ {
+#ifdef SYSLOG_FAILURE
+ log_su (pw, false);
+#endif
+ sleep (getdef_num ("FAIL_DELAY", 1));
+ error (EXIT_CANCELED, 0, _("incorrect password"));
+ }
+#ifdef SYSLOG_SUCCESS
+ else
+ {
+ log_su (pw, true);
+ }
+#endif
+
+ if (request_same_session || !command || !pw->pw_uid)
+ same_session = 1;
+
+ if (!shell && !change_environment)
+ shell = getenv ("SHELL");
+ if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
+ {
+ /* The user being su'd to has a nonstandard shell, and so is
+ probably a uucp account or has restricted access. Don't
+ compromise the account by allowing access with a standard
+ shell. */
+ error (0, 0, _("using restricted shell %s"), pw->pw_shell);
+ shell = NULL;
+ }
+ shell = xstrdup (shell ? shell : pw->pw_shell);
+
+ init_groups (pw);
+
+#ifdef USE_PAM
+ create_watching_parent ();
+ /* Now we're in the child. */
+#endif
+
+ change_identity (pw);
+
+ if (!same_session)
+ setsid ();
+
+ /* 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);
+
+ /* error() flushes stderr, but does not check for write failure.
+ Normally, we would catch this via our atexit() hook of
+ close_stdout, but execv() gets in the way. If stderr
+ encountered a write failure, there is no need to try calling
+ error() again. */
+ if (ferror (stderr))
+ exit (EXIT_CANCELED);
+
+ run_shell (shell, command, argv + optind, MAX (0, argc - optind));
+}
Index: src/getdef.h
===================================================================
--- /dev/null
+++ src/getdef.h
@@ -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 or
+ later 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/getdef.c
===================================================================
--- /dev/null
+++ src/getdef.c
@@ -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 or
+ later 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: doc/coreutils.texi
===================================================================
--- doc/coreutils.texi.orig
+++ doc/coreutils.texi
@@ -109,6 +109,7 @@
* stat: (coreutils)stat invocation. Report file(system) status.
* stdbuf: (coreutils)stdbuf invocation. Modify stdio buffering.
* stty: (coreutils)stty invocation. Print/change terminal settings.
+* su: (coreutils)su invocation. Modify user and group ID.
* sum: (coreutils)sum invocation. Print traditional checksum.
* sync: (coreutils)sync invocation. Synchronize memory and disk.
* tac: (coreutils)tac invocation. Reverse files.
@@ -199,7 +200,7 @@ Free Documentation License''.
* User information:: id logname whoami groups users who
* System context:: date arch nproc uname hostid uptime
* SELinux context:: chcon runcon
-* Modified command invocation:: chroot env nice nohup stdbuf timeout
+* Modified command invocation:: chroot env nice nohup stdbuf su timeout
* Process control:: kill
* Delaying:: sleep
* Numeric operations:: factor seq
@@ -444,6 +445,7 @@ Modified command invocation
* nice invocation:: Run a command with modified niceness
* nohup invocation:: Run a command immune to hangups
* stdbuf invocation:: Run a command with modified I/O buffering
+* su invocation:: Run a command with substitute user and group ID
* timeout invocation:: Run a command with a time limit
Process control
@@ -767,7 +769,8 @@ meanings with the values @samp{0} and @s
Here are some of the exceptions:
@command{chroot}, @command{env}, @command{expr}, @command{nice},
@command{nohup}, @command{numfmt}, @command{printenv}, @command{sort},
-@command{stdbuf}, @command{test}, @command{timeout}, @command{tty}.
+@command{su}, @command{stdbuf}, @command{test}, @command{timeout},
+@command{tty}.
@node Backup options
@@ -15762,6 +15765,7 @@ user, etc.
* nice invocation:: Modify niceness.
* nohup invocation:: Immunize to hangups.
* stdbuf invocation:: Modify buffering of standard streams.
+* su invocation:: Modify user and group ID.
* timeout invocation:: Run with time limit.
@end menu
@@ -16293,6 +16297,149 @@ the exit status of @var{command} otherwi
@end display
+@node su invocation
+@section @command{su}: Run a command with substitute user and group ID
+
+@pindex su
+@cindex substitute user and group IDs
+@cindex user ID, switching
+@cindex super-user, becoming
+@cindex root, becoming
+
+@command{su} allows one user to temporarily become another user. It runs a
+command (often an interactive shell) with the real and effective user
+ID, group ID, and supplemental groups of a given @var{user}. Synopsis:
+
+@example
+su [@var{option}]@dots{} [@var{user} [@var{arg}]@dots{}]
+@end example
+
+@cindex passwd entry, and @command{su} shell
+@flindex /bin/sh
+@flindex /etc/passwd
+If no @var{user} is given, the default is @code{root}, the super-user.
+The shell to use is taken from @var{user}'s @code{passwd} entry, or
+@file{/bin/sh} if none is specified there. If @var{user} has a
+password, @command{su} prompts for the password unless run by a user with
+effective user ID of zero (the super-user).
+
+@vindex HOME
+@vindex SHELL
+@vindex USER
+@vindex LOGNAME
+@cindex login shell
+By default, @command{su} does not change the current directory.
+It sets the environment variables @env{HOME} and @env{SHELL}
+from the password entry for @var{user}, and if @var{user} is not
+the super-user, sets @env{USER} and @env{LOGNAME} to @var{user}.
+By default, the shell is not a login shell.
+
+Any additional @var{arg}s are passed as additional arguments to the
+shell.
+
+@cindex @option{-su}
+GNU @command{su} does not treat @file{/bin/sh} or any other shells specially
+(e.g., by setting @code{argv[0]} to @option{-su}, passing @option{-c} only
+to certain shells, etc.).
+
+@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}.)
+
+This version of @command{su} has support for using PAM for
+authentication. You can edit @file{/etc/pam.d/su} resp @file{/etc/pam.d/su-l}
+to customize its behaviour.
+
+If the environment variable SU_COMMAND_SAME_SESSION is set, @command{su} su will
+not open a new session for running a command thus making @option{-c} behaves
+just like @option{--session-command}.
+
+The program accepts the following options. Also see @ref{Common options}.
+
+@table @samp
+@item -c @var{command}
+@itemx --command=@var{command}
+@opindex -c
+@opindex --command
+Pass @var{command}, a single command line to run, to the shell with
+a @option{-c} option instead of starting an interactive shell.
+
+@itemx --session-command=@var{command}
+@opindex --session-command
+Pass @var{command}, a single command line to run, to the shell with a
+@option{-c} option instead of starting an interactive and do not create
+a new session for it.
+
+@item -f
+@itemx --fast
+@opindex -f
+@opindex --fast
+@flindex .cshrc
+@cindex file name pattern expansion, disabled
+@cindex globbing, disabled
+Pass the @option{-f} option to the shell. This probably only makes sense
+if the shell run is @command{csh} or @command{tcsh}, for which the @option{-f}
+option prevents reading the startup file (@file{.cshrc}). With
+Bourne-like shells, the @option{-f} option disables file name pattern
+expansion (globbing), which is not likely to be useful.
+
+@item -
+@itemx -l
+@itemx --login
+@opindex -
+@opindex -l
+@opindex --login
+@c other variables already indexed above
+@vindex TERM
+@vindex PATH
+@cindex login shell, creating
+Make the shell a login shell. This means the following. Unset all
+environment variables except @env{TERM}, @env{HOME}, and @env{SHELL}
+(which are set as described above), and @env{USER} and @env{LOGNAME}
+(which are set, even for the super-user, as described above), and set
+@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).
+
+@item -m
+@itemx -p
+@itemx --preserve-environment
+@opindex -m
+@opindex -p
+@opindex --preserve-environment
+@cindex environment, preserving
+@flindex /etc/shells
+@cindex restricted shell
+Do not change the environment variables @env{HOME}, @env{USER},
+@env{LOGNAME}, or @env{SHELL}. Run the shell given in the environment
+variable @env{SHELL} instead of the shell from @var{user}'s passwd
+entry, unless the user running @command{su} is not the super-user and
+@var{user}'s shell is restricted. A @dfn{restricted shell} is one that
+is not listed in the file @file{/etc/shells}, or in a compiled-in list
+if that file does not exist. Parts of what this option does can be
+overridden by @option{--login} and @option{--shell}.
+
+@item -s @var{shell}
+@itemx --shell=@var{shell}
+@opindex -s
+@opindex --shell
+Run @var{shell} instead of the shell from @var{user}'s passwd entry,
+unless the user running @command{su} is not the super-user and @var{user}'s
+shell is restricted (see @option{-m} just above).
+
+@end table
+
+@cindex exit status of @command{su}
+Exit status:
+
+@display
+125 if @command{su} itself fails
+126 if subshell is found but cannot be invoked
+127 if subshell cannot be found
+the exit status of the subshell otherwise
+@end display
+
@node timeout invocation
@section @command{timeout}: Run a command with a time limit
Index: man/local.mk
===================================================================
--- man/local.mk.orig
+++ man/local.mk
@@ -26,6 +26,7 @@ run_help2man = $(SHELL) $(srcdir)/man/du
endif
man1_MANS = @man1_MANS@
+man1_MANS += man/su.1
EXTRA_DIST += $(man1_MANS:.1=.x)
EXTRA_MANS = @EXTRA_MANS@
@@ -144,6 +145,7 @@ man/split.1: src/split
man/stat.1: src/stat
man/stdbuf.1: src/stdbuf
man/stty.1: src/stty
+man/su.1: src/su
man/sum.1: src/sum
man/sync.1: src/sync
man/tac.1: src/tac
Index: man/su.x
===================================================================
--- /dev/null
+++ man/su.x
@@ -0,0 +1,4 @@
+[NAME]
+su \- run a shell with substitute user and group IDs
+[DESCRIPTION]
+.\" Add any additional description here
Index: tests/misc/su-fail.sh
===================================================================
--- /dev/null
+++ tests/misc/su-fail.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# Test su failure cases
+
+# Copyright (C) 2009-2012 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# 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, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ su
+
+#require_built_ su
+
+# Very little that we can test without a root password
+su --- / true # unknown option
+test $? = 125 || fail=1
+su no_such_user
+test $? = 125 || fail=1
+
+Exit $fail
Index: tests/local.mk
===================================================================
--- tests/local.mk.orig
+++ tests/local.mk
@@ -361,6 +361,7 @@ all_tests = \
tests/misc/stty-invalid.sh \
tests/misc/stty-pairs.sh \
tests/misc/stty-row-col.sh \
+ tests/misc/su-fail.sh \
tests/misc/sum.pl \
tests/misc/sum-sysv.sh \
tests/misc/tac.pl \
Index: po/coreutils.pot
===================================================================
--- po/coreutils.pot.orig
+++ po/coreutils.pot
@@ -8461,6 +8461,73 @@ msgstr ""
msgid "invalid integer argument %s"
msgstr ""
+#: src/su.c:229
+msgid "Password:"
+msgstr ""
+
+#: src/su.c:232
+#, c-format
+msgid "getpass: cannot open /dev/tty"
+msgstr ""
+
+#: src/su.c:290
+#, c-format
+msgid "cannot set groups"
+msgstr ""
+
+#: src/su.c:294
+#, c-format
+msgid "cannot set group id"
+msgstr ""
+
+#: src/su.c:296
+#, c-format
+msgid "cannot set user id"
+msgstr ""
+
+#: src/su.c:371
+#, c-format
+msgid "Usage: %s [OPTION]... [-] [USER [ARG]...]\n"
+msgstr ""
+
+#: src/su.c:372
+msgid ""
+"Change the effective user id and group id to that of USER.\n"
+"\n"
+" -, -l, --login make the shell a login shell\n"
+" -c, --command=COMMAND pass a single COMMAND to the shell with -c\n"
+" -f, --fast pass -f to the shell (for csh or tcsh)\n"
+" -m, --preserve-environment do not reset environment variables\n"
+" -p same as -m\n"
+" -s, --shell=SHELL run SHELL if /etc/shells allows it\n"
+msgstr ""
+
+#: src/su.c:384
+msgid ""
+"\n"
+"A mere - implies -l. If USER not given, assume root.\n"
+msgstr ""
+
+#: src/su.c:461
+#, c-format
+msgid "user %s does not exist"
+msgstr ""
+
+#: src/su.c:484
+#, c-format
+msgid "incorrect password"
+msgstr ""
+
+#: src/su.c:501
+#, c-format
+msgid "using restricted shell %s"
+msgstr ""
+
+#: src/su.c:509
+#, c-format
+msgid "warning: cannot change directory to %s"
+msgstr ""
+
#. This is a proper name. See the gettext manual, section Names.
#: src/sum.c:37
msgid "Kayvan Aghaiepour"
Index: README
===================================================================
--- README.orig
+++ README
@@ -13,7 +13,7 @@ The programs that can be built with this
link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup
nproc numfmt od paste pathchk pinky pr printenv printf ptx pwd readlink
realpath rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum sha512sum
- shred shuf sleep sort split stat stdbuf stty sum sync tac tail tee test
+ shred shuf sleep sort split stat stdbuf stty su sum sync tac tail tee test
timeout touch tr true truncate tsort tty uname unexpand uniq unlink
uptime users vdir wc who whoami yes