mirror of
git://git.sv.gnu.org/coreutils
synced 2026-06-04 23:04:48 -04:00
d2940e7cd4
the callee doesn't have to count 'em. All callers changed. Don't allocate more space for the arg vector than we'll need. Use memcpy to copy the args rather than rolling our own loop. Use size_t for sizes. (fast_startup, simulate_login, change_environment, log_su, correct_password, restricted_shell, main): Use bool for booleans. (longopts): Don't assume change_environment is an int. Use NULL, not 0, for pointers. (xsetenv): New function, replacing xputenv and concat. All callers changed. (elements): Remove; no longer needed. (log_su, correct_passwd, main): Prefer !x to x==NULL. (log_su): 2 -> STDERR_FILENO. (modify_environment, main): Don't assume that getenv's returned value has an indefinite lifetime. (modify_environment): Allocate a larger environ. (main): Remove an impossible 'case 0'; if it happens now, it'll get diagnosed. Don't assume getpwnam results outlive endpwent. Check for null or empty pw_name, pw_dir and for null pw_passwd.
550 lines
15 KiB
C
550 lines
15 KiB
C
/* su for GNU. Run a shell with substitute user and group IDs.
|
|
Copyright (C) 1992-2004 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 2, 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, write to the Free Software Foundation,
|
|
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
|
|
|
/* 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.
|
|
|
|
Options:
|
|
-, -l, --login Make the subshell a login shell.
|
|
Unset all environment variables except
|
|
TERM, HOME and SHELL (set as above), and USER
|
|
and LOGNAME (set unconditionally as above), and
|
|
set PATH to a default value.
|
|
Change to USER's home directory.
|
|
Prepend "-" to the shell's name.
|
|
-c, --commmand=COMMAND
|
|
Pass COMMAND to the subshell with a -c option
|
|
instead of starting an interactive shell.
|
|
-f, --fast Pass the -f option to the subshell.
|
|
-m, -p, --preserve-environment
|
|
Do not change HOME, USER, LOGNAME, SHELL.
|
|
Run $SHELL instead of USER's shell from /etc/passwd
|
|
unless not the superuser and USER's shell is
|
|
restricted.
|
|
Overridden by --login and --shell.
|
|
-s, --shell=shell Run SHELL instead of USER's shell from /etc/passwd
|
|
unless not the superuser and USER's shell is
|
|
restricted.
|
|
|
|
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>
|
|
|
|
/* Hide any system prototype for getusershell.
|
|
This is necessary because some Cray systems have a conflicting
|
|
prototype (returning `int') in <unistd.h>. */
|
|
#define getusershell _getusershell_sys_proto_
|
|
|
|
#include "system.h"
|
|
#include "dirname.h"
|
|
|
|
#undef getusershell
|
|
|
|
#if HAVE_SYSLOG_H && HAVE_SYSLOG
|
|
# include <syslog.h>
|
|
#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 "David MacKenzie"
|
|
|
|
#if HAVE_PATHS_H
|
|
# include <paths.h>
|
|
#endif
|
|
|
|
/* 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
|
|
|
|
/* 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
|
|
|
|
/* 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"
|
|
|
|
char *crypt ();
|
|
char *getpass ();
|
|
char *getusershell ();
|
|
void endusershell ();
|
|
void setusershell ();
|
|
|
|
extern char **environ;
|
|
|
|
static void run_shell (char const *, char const *, char **, size_t)
|
|
ATTRIBUTE_NORETURN;
|
|
|
|
/* The name this program was run with. */
|
|
char *program_name;
|
|
|
|
/* 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;
|
|
|
|
static struct option const longopts[] =
|
|
{
|
|
{"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. */
|
|
struct passwd *pwd = getpwuid (getuid ());
|
|
old_user = (pwd ? pwd->pw_name : "");
|
|
}
|
|
tty = ttyname (STDERR_FILENO);
|
|
if (!tty)
|
|
tty = "none";
|
|
/* 4.2BSD openlog doesn't have the third parameter. */
|
|
openlog (base_name (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
|
|
|
|
/* Ask the user for a password.
|
|
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)
|
|
{
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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
|
|
? DEFAULT_LOGIN_PATH
|
|
: 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 (pw->pw_uid)
|
|
{
|
|
xsetenv ("USER", pw->pw_name);
|
|
xsetenv ("LOGNAME", pw->pw_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Become the user and group(s) specified by PW. */
|
|
|
|
static void
|
|
change_identity (const struct passwd *pw)
|
|
{
|
|
#ifdef HAVE_INITGROUPS
|
|
errno = 0;
|
|
if (initgroups (pw->pw_name, pw->pw_gid) == -1)
|
|
error (EXIT_FAIL, errno, _("cannot set groups"));
|
|
endgrent ();
|
|
#endif
|
|
if (setgid (pw->pw_gid))
|
|
error (EXIT_FAIL, errno, _("cannot set group id"));
|
|
if (setuid (pw->pw_uid))
|
|
error (EXIT_FAIL, 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 = base_name (shell);
|
|
arg0 = xmalloc (strlen (shell_basename) + 2);
|
|
arg0[0] = '-';
|
|
strcpy (arg0 + 1, shell_basename);
|
|
args[0] = arg0;
|
|
}
|
|
else
|
|
args[0] = base_name (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)
|
|
fprintf (stderr, _("Try `%s --help' for more information.\n"),
|
|
program_name);
|
|
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, --commmand=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\
|
|
"), 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);
|
|
printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
|
|
}
|
|
exit (status);
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
int optc;
|
|
const char *new_user = DEFAULT_USER;
|
|
char *command = NULL;
|
|
char *shell = NULL;
|
|
struct passwd *pw;
|
|
struct passwd pw_copy;
|
|
|
|
initialize_main (&argc, &argv);
|
|
program_name = argv[0];
|
|
setlocale (LC_ALL, "");
|
|
bindtextdomain (PACKAGE, LOCALEDIR);
|
|
textdomain (PACKAGE);
|
|
|
|
initialize_exit_failure (EXIT_FAIL);
|
|
atexit (close_stdout);
|
|
|
|
fast_startup = false;
|
|
simulate_login = false;
|
|
change_environment = true;
|
|
|
|
while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
|
|
{
|
|
switch (optc)
|
|
{
|
|
case 'c':
|
|
command = optarg;
|
|
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_FAIL);
|
|
}
|
|
}
|
|
|
|
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_FAIL, 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. 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
|
|
error (EXIT_FAIL, 0, _("incorrect password"));
|
|
}
|
|
#ifdef SYSLOG_SUCCESS
|
|
else
|
|
{
|
|
log_su (pw, true);
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
modify_environment (pw, shell);
|
|
|
|
change_identity (pw);
|
|
if (simulate_login && chdir (pw->pw_dir) != 0)
|
|
error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
|
|
|
|
run_shell (shell, command, argv + optind, MAX (0, argc - optind));
|
|
}
|