shadow/src/su.c

941 lines
24 KiB
C
Raw Normal View History

/*
* Copyright (c) 1989 - 1994, Julianne Frances Haugh
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
* Copyright (c) 2000 - 2006, Tomasz Kłoczko
* Copyright (c) 2007 - 2008, Nicolas François
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the copyright holders or contributors may not be used to
* endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Some parts substantially derived from an ancestor of:
su for GNU. Run a shell with substitute user and group IDs.
Copyright (C) 1992-2003 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., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA. */
#include <config.h>
#ident "$Id$"
#include <getopt.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include "prototypes.h"
#include "defines.h"
#include "exitcodes.h"
#include "pwauth.h"
#include "getdef.h"
#ifdef USE_PAM
#include "pam_defs.h"
#endif
/*
* Assorted #defines to control su's behavior
*/
/*
* Global variables
*/
/* not needed by sulog.c anymore */
static char name[BUFSIZ];
static char oldname[BUFSIZ];
/* If nonzero, change some environment vars to indicate the user su'd to. */
static bool change_environment;
#ifdef USE_PAM
static pam_handle_t *pamh = NULL;
static bool caught = false;
#endif
static char *Prog;
extern struct passwd pwent;
/*
* External identifiers
*/
extern char **newenvp;
extern char **environ;
extern size_t newenvc;
/* local function prototypes */
#ifndef USE_PAM
static RETSIGTYPE die (int);
static int iswheel (const char *);
/*
* die - set or reset termio modes.
*
* die() is called before processing begins. signal() is then called
* with die() as the signal handler. If signal later calls die() with a
* signal number, the terminal modes are then reset.
*/
static RETSIGTYPE die (int killed)
{
static TERMIO sgtty;
if (killed)
STTY (0, &sgtty);
else
GTTY (0, &sgtty);
if (killed) {
closelog ();
exit (killed);
}
}
static int iswheel (const char *username)
{
struct group *grp;
* lib/prototypes.h, configure.in, libmisc/Makefile.am, libmisc/xgetXXbyYY.c, libmisc/xgetpwnam.c, libmisc/xgetpwuid.c, libmisc/xgetgrnam.c, libmisc/xgetgrgid.c, libmisc/xgetspnam.c: Added functions xgetpwnam(), xgetpwuid(), xgetgrnam(), xgetgrgid(), and xgetspnam(). They allocate memory for the returned structure and are more robust to successive calls. They are implemented with the libc's getxxyyy_r() functions if available. * libmisc/limits.c, libmisc/entry.c, libmisc/chowntty.c, libmisc/addgrps.c, libmisc/myname.c, libmisc/rlogin.c, libmisc/pwdcheck.c, src/newgrp.c, src/login_nopam.c, src/userdel.c, src/lastlog.c, src/grpck.c, src/gpasswd.c, src/newusers.c, src/chpasswd.c, src/chfn.c, src/groupmems.c, src/usermod.c, src/expiry.c, src/groupdel.c, src/chgpasswd.c, src/su.c, src/useradd.c, src/groupmod.c, src/passwd.c, src/pwck.c, src/groupadd.c, src/chage.c, src/login.c, src/suauth.c, src/faillog.c, src/groups.c, src/chsh.c, src/id.c: Review all the usage of one of the getpwnam(), getpwuid(), getgrnam(), getgrgid(), and getspnam() functions. It was noticed on http://bugs.debian.org/341230 that chfn and chsh use a passwd structure after calling a pam function, which result in using information from the passwd structure requested by pam, not the original one. It is much easier to use the new xget... functions to avoid these issues. I've checked which call to the original get... functions could be left (reducing the scope of the structure if possible), and I've left comments to ease future reviews (e.g. /* local, no need for xgetpwnam */). Note: the getpwent/getgrent calls should probably be checked also. * src/groupdel.c, src/expiry.c: Fix typos in comments. * src/groupmod.c: Re-indent. * libmisc/Makefile.am, lib/groupmem.c, lib/groupio.c, lib/pwmem.c, lib/pwio.c, lib/shadowmem.c, lib/shadowio.c: Move the __<xx>_dup functions (used by the xget... functions) from the <xx>io.c files to the new <xx>mem.c files. This avoid linking some utils against the SELinux library.
2007-11-19 04:45:26 +05:30
grp = getgrnam ("wheel"); /* !USE_PAM, no need for xgetgrnam */
if (!grp || !grp->gr_mem)
return 0;
return is_on_list (grp->gr_mem, username);
}
#endif /* !USE_PAM */
/* borrowed from GNU sh-utils' "su.c" */
static bool restricted_shell (const char *shellstr)
{
char *line;
setusershell ();
while ((line = getusershell ()) != NULL) {
if (('#' != *line) && (strcmp (line, shellstr) == 0)) {
endusershell ();
return false;
}
}
endusershell ();
return true;
}
static void su_failure (const char *tty)
{
sulog (tty, 0, oldname, name); /* log failed attempt */
#ifdef USE_SYSLOG
if (getdef_bool ("SYSLOG_SU_ENAB"))
SYSLOG (((0 != pwent.pw_uid) ? LOG_INFO : LOG_NOTICE,
"- %s %s:%s", tty,
('\0' != oldname[0]) ? oldname : "???",
('\0' != name[0]) ? name : "???"));
closelog ();
#endif
exit (1);
}
#ifdef USE_PAM
/* Signal handler for parent process later */
static void catch_signals (unused int sig)
{
caught = true;
}
/* This I ripped out of su.c from sh-utils after the Mandrake pam patch
* have been applied. Some work was needed to get it integrated into
* su.c from shadow.
*/
static void run_shell (const char *shellstr, char *args[], bool doshell,
char *const envp[])
{
pid_t child;
sigset_t ourset;
int status;
int ret;
child = fork ();
if (child == 0) { /* child shell */
/*
* PAM_DATA_SILENT is not supported by some modules, and
* there is no strong need to clean up the process space's
* memory since we will either call exec or exit.
pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
*/
if (doshell) {
(void) shell (shellstr, (char *) args[0], envp);
} else {
(void) execve (shellstr, (char **) args, envp);
}
exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
} else if ((pid_t)-1 == child) {
(void) fprintf (stderr, "%s: Cannot fork user shell\n", Prog);
SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));
closelog ();
exit (1);
}
/* parent only */
sigfillset (&ourset);
if (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0) {
(void) fprintf (stderr, "%s: signal malfunction\n", Prog);
caught = true;
}
if (!caught) {
struct sigaction action;
action.sa_handler = catch_signals;
sigemptyset (&action.sa_mask);
action.sa_flags = 0;
sigemptyset (&ourset);
if ( (sigaddset (&ourset, SIGTERM) != 0)
|| (sigaddset (&ourset, SIGALRM) != 0)
|| (sigaction (SIGTERM, &action, NULL) != 0)
|| (sigprocmask (SIG_UNBLOCK, &ourset, NULL) != 0)
) {
fprintf (stderr,
"%s: signal masking malfunction\n", Prog);
caught = true;
}
}
if (!caught) {
do {
pid_t pid;
pid = waitpid (-1, &status, WUNTRACED);
if (((pid_t)-1 != pid) && (0 != WIFSTOPPED (status))) {
/* The child (shell) was suspended.
* Suspend su. */
kill (getpid (), WSTOPSIG(status));
/* wake child when resumed */
kill (pid, SIGCONT);
}
} while (0 != WIFSTOPPED (status));
}
if (caught) {
fprintf (stderr, "\nSession terminated, killing shell...");
kill (child, SIGTERM);
}
ret = pam_close_session (pamh, 0);
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_close_session: %s",
pam_strerror (pamh, ret)));
fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
(void) pam_end (pamh, ret);
exit (1);
}
ret = pam_end (pamh, PAM_SUCCESS);
if (caught) {
sleep (2);
kill (child, SIGKILL);
fprintf (stderr, " ...killed.\n");
exit (-1);
}
exit ((0 != WIFEXITED (status)) ? WEXITSTATUS (status)
: WTERMSIG (status) + 128);
}
#endif
/*
* usage - print command line syntax and exit
*/
static void usage (void)
{
fputs (_("Usage: su [options] [LOGIN]\n"
2008-01-25 02:24:42 +05:30
"\n"
"Options:\n"
" -c, --command COMMAND pass COMMAND to the invoked shell\n"
" -h, --help display this help message and exit\n"
" -, -l, --login make the shell a login shell\n"
" -m, -p,\n"
" --preserve-environment do not reset environment variables, and\n"
" keep the same shell\n"
" -s, --shell SHELL use SHELL instead of the default in passwd\n"
"\n"), stderr);
exit (E_USAGE);
}
/*
* su - switch user id
*
* su changes the user's ids to the values for the specified user. if
* no new user name is specified, "root" is used by default.
*
* Any additional arguments are passed to the user's shell. In
* particular, the argument "-c" will cause the next argument to be
* interpreted as a command by the common shell programs.
*/
int main (int argc, char **argv)
{
char *cp;
const char *tty = NULL; /* Name of tty SU is run from */
bool doshell = false;
bool fakelogin = false;
bool amroot = false;
uid_t my_uid;
struct passwd *pw = NULL;
char **envp = environ;
char *shellstr = NULL;
char *command = NULL;
#ifdef USE_PAM
char **envcp;
int ret;
#else /* !USE_PAM */
int err = 0;
RETSIGTYPE (*oldsig) (int);
int is_console = 0;
struct spwd *spwd = 0;
#ifdef SU_ACCESS
char *oldpass;
#endif
#endif /* !USE_PAM */
sanitize_env ();
(void) setlocale (LC_ALL, "");
(void) bindtextdomain (PACKAGE, LOCALEDIR);
(void) textdomain (PACKAGE);
change_environment = true;
/*
* Get the program name. The program name is used as a prefix to
* most error messages.
*/
Prog = Basename (argv[0]);
OPENLOG ("su");
/*
* Process the command line arguments.
*/
{
/*
* Parse the command line options.
*/
int option_index = 0;
int c;
static struct option long_options[] = {
{"command", required_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
{"login", no_argument, NULL, 'l'},
{"preserve-environment", no_argument, NULL, 'p'},
{"shell", required_argument, NULL, 's'},
{NULL, 0, NULL, '\0'}
};
while ((c =
getopt_long (argc, argv, "c:hlmps:", long_options,
&option_index)) != -1) {
switch (c) {
case 'c':
command = optarg;
break;
case 'h':
usage ();
break;
case 'l':
fakelogin = true;
break;
case 'm':
case 'p':
/* This will only have an effect if the target
* user do not have a restricted shell, or if
* su is called by root.
*/
change_environment = false;
break;
case 's':
shellstr = optarg;
break;
default:
usage (); /* NOT REACHED */
}
}
if ((optind < argc) && (strcmp (argv[optind], "-") == 0)) {
fakelogin = true;
optind++;
if ( (optind < argc)
&& (strcmp (argv[optind], "--") == 0)) {
optind++;
}
}
}
initenv ();
my_uid = getuid ();
amroot = (my_uid == 0);
/*
* Get the tty name. Entries will be logged indicating that the user
* tried to change to the named new user from the current terminal.
*/
cp = ttyname (0);
if ((isatty (0) != 0) && (NULL != cp)) {
if (strncmp (cp, "/dev/", 5) == 0) {
tty = cp + 5;
} else {
tty = cp;
}
#ifndef USE_PAM
is_console = console (tty);
#endif
} else {
/*
* Be more paranoid, like su from SimplePAMApps. --marekm
*/
if (!amroot) {
fprintf (stderr,
_("%s: must be run from a terminal\n"), Prog);
exit (1);
}
tty = "???";
}
/*
* The next argument must be either a user ID, or some flag to a
* subshell. Pretty sticky since you can't have an argument which
* doesn't start with a "-" unless you specify the new user name.
* Any remaining arguments will be passed to the user's login shell.
*/
if ((optind < argc) && ('-' != argv[optind][0])) {
STRFCPY (name, argv[optind++]); /* use this login id */
if ((optind < argc) && (strcmp (argv[optind], "--") == 0)) {
optind++;
}
}
if ('\0' == name[0]) { /* use default user ID */
(void) strcpy (name, "root");
}
doshell = (argc == optind); /* any arguments remaining? */
if (NULL != command) {
doshell = false;
}
/*
* Get the user's real name. The current UID is used to determine
* who has executed su. That user ID must exist.
*/
pw = get_my_pwent ();
if (NULL == pw) {
SYSLOG ((LOG_CRIT, "Unknown UID: %u", my_uid));
su_failure (tty);
}
STRFCPY (oldname, pw->pw_name);
#ifndef USE_PAM
#ifdef SU_ACCESS
/*
* Sort out the password of user calling su, in case needed later
* -- chris
*/
spwd = getspnam (oldname); /* !USE_PAM, no need for xgetspnam */
if (NULL != spwd) {
pw->pw_passwd = spwd->sp_pwdp;
}
oldpass = xstrdup (pw->pw_passwd);
#endif /* SU_ACCESS */
#else /* USE_PAM */
ret = pam_start ("su", name, &conv, &pamh);
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_start: error %d", ret);
fprintf (stderr, _("%s: pam_start: error %d\n"),
Prog, ret));
exit (1);
}
ret = pam_set_item (pamh, PAM_TTY, (const void *) tty);
if (PAM_SUCCESS == ret) {
ret = pam_set_item (pamh, PAM_RUSER, (const void *) oldname);
}
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_set_item: %s",
pam_strerror (pamh, ret)));
fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
pam_end (pamh, ret);
exit (1);
}
#endif /* USE_PAM */
top:
/*
* This is the common point for validating a user whose name is
* known. It will be reached either by normal processing, or if the
* user is to be logged into a subsystem root.
*
* The password file entries for the user is gotten and the account
* validated.
*/
pw = xgetpwnam (name);
if (NULL == pw) {
(void) fprintf (stderr, _("Unknown id: %s\n"), name);
closelog ();
exit (1);
}
#ifndef USE_PAM
spwd = NULL;
if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
spwd = getspnam (name); /* !USE_PAM, no need for xgetspnam */
if (NULL != spwd) {
pw->pw_passwd = spwd->sp_pwdp;
}
}
#endif /* !USE_PAM */
pwent = *pw;
/* If su is not called by root, and the target user has a restricted
* shell, the environment must be changed.
*/
change_environment |= (restricted_shell (pwent.pw_shell) && !amroot);
/*
* If a new login is being set up, the old environment will be
* ignored and a new one created later on.
* (note: in the case of a subsystem, the shell will be restricted,
* and this won't be executed on the first pass)
*/
if (fakelogin && change_environment) {
/*
* The terminal type will be left alone if it is present in
* the environment already.
*/
cp = getenv ("TERM");
if (NULL != cp) {
addenv ("TERM", cp);
}
#ifndef USE_PAM
cp = getdef_str ("ENV_TZ");
if (NULL != cp) {
addenv (('/' == *cp) ? tz (cp) : cp, NULL);
}
/*
* The clock frequency will be reset to the login value if required
*/
cp = getdef_str ("ENV_HZ");
if (NULL != cp) {
addenv (cp, NULL); /* set the default $HZ, if one */
}
/*
* Also leave DISPLAY and XAUTHORITY if present, else
* pam_xauth will not work.
*/
cp = getenv ("DISPLAY");
if (NULL != cp) {
addenv ("DISPLAY", cp);
}
cp = getenv ("XAUTHORITY");
if (NULL != cp) {
addenv ("XAUTHORITY", cp);
}
#endif /* !USE_PAM */
} else {
while (NULL != *envp) {
addenv (*envp, NULL);
envp++;
}
}
#ifndef USE_PAM
/*
* BSD systems only allow "wheel" to SU to root. USG systems don't,
* so we make this a configurable option.
*/
/* The original Shadow 3.3.2 did this differently. Do it like BSD:
*
* - check for UID 0 instead of name "root" - there are systems with
* several root accounts under different names,
*
* - check the contents of /etc/group instead of the current group
* set (you must be listed as a member, GID 0 is not sufficient).
*
* In addition to this traditional feature, we now have complete su
* access control (allow, deny, no password, own password). Thanks
* to Chris Evans <lady0110@sable.ox.ac.uk>.
*/
if (!amroot) {
if ( (0 == pwent.pw_uid)
&& getdef_bool ("SU_WHEEL_ONLY")
&& !iswheel (oldname)) {
fprintf (stderr,
_("You are not authorized to su %s\n"), name);
exit (1);
}
#ifdef SU_ACCESS
switch (check_su_auth (oldname, name)) {
case 0: /* normal su, require target user's password */
break;
case 1: /* require no password */
pwent.pw_passwd = ""; /* XXX warning: const */
break;
case 2: /* require own password */
puts (_("(Enter your own password)"));
pwent.pw_passwd = oldpass;
break;
default: /* access denied (-1) or unexpected value */
fprintf (stderr,
_("You are not authorized to su %s\n"), name);
exit (1);
}
#endif /* SU_ACCESS */
}
#endif /* !USE_PAM */
/* If the user do not want to change the environment,
* use the current SHELL.
* (unless another shell is required by the command line)
*/
if ((NULL == shellstr) && !change_environment) {
shellstr = getenv ("SHELL");
}
/* For users with non null UID, if this user has a restricted
* shell, the shell must be the one specified in /etc/passwd
*/
if ( (NULL != shellstr)
&& !amroot
&& restricted_shell (pwent.pw_shell)) {
shellstr = NULL;
}
/* If the shell is not set at this time, use the shell specified
* in /etc/passwd.
*/
if (NULL == shellstr) {
shellstr = (char *) strdup (pwent.pw_shell);
}
/*
* Set the default shell.
*/
if ((NULL == shellstr) || ('\0' == shellstr[0])) {
shellstr = "/bin/sh";
}
(void) signal (SIGINT, SIG_IGN);
(void) signal (SIGQUIT, SIG_IGN);
#ifdef USE_PAM
ret = pam_authenticate (pamh, 0);
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_authenticate: %s",
pam_strerror (pamh, ret)));
fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
(void) pam_end (pamh, ret);
su_failure (tty);
}
ret = pam_acct_mgmt (pamh, 0);
if (PAM_SUCCESS != ret) {
if (amroot) {
fprintf (stderr, _("%s: %s\n(Ignored)\n"), Prog,
pam_strerror (pamh, ret));
} else if (PAM_NEW_AUTHTOK_REQD == ret) {
ret = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_chauthtok: %s",
pam_strerror (pamh, ret)));
fprintf (stderr, _("%s: %s\n"), Prog,
pam_strerror (pamh, ret));
(void) pam_end (pamh, ret);
su_failure (tty);
}
} else {
SYSLOG ((LOG_ERR, "pam_acct_mgmt: %s",
pam_strerror (pamh, ret)));
fprintf (stderr, _("%s: %s\n"), Prog,
pam_strerror (pamh, ret));
(void) pam_end (pamh, ret);
su_failure (tty);
}
}
#else /* !USE_PAM */
/*
* Set up a signal handler in case the user types QUIT.
*/
die (0);
(void) oldsig = signal (SIGQUIT, die);
/*
* See if the system defined authentication method is being used.
* The first character of an administrator defined method is an '@'
* character.
*/
if (!amroot && pw_auth (pwent.pw_passwd, name, PW_SU, (char *) 0)) {
SYSLOG ((pwent.pw_uid ? LOG_NOTICE : LOG_WARN,
"Authentication failed for %s", name));
su_failure (tty);
}
(void) signal (SIGQUIT, oldsig);
/*
* Check to see if the account is expired. root gets to ignore any
* expired accounts, but normal users can't become a user with an
* expired password.
*/
if (!amroot) {
if (NULL == spwd) {
spwd = pwd_to_spwd (&pwent);
}
if (expire (&pwent, spwd)) {
* lib/prototypes.h, configure.in, libmisc/Makefile.am, libmisc/xgetXXbyYY.c, libmisc/xgetpwnam.c, libmisc/xgetpwuid.c, libmisc/xgetgrnam.c, libmisc/xgetgrgid.c, libmisc/xgetspnam.c: Added functions xgetpwnam(), xgetpwuid(), xgetgrnam(), xgetgrgid(), and xgetspnam(). They allocate memory for the returned structure and are more robust to successive calls. They are implemented with the libc's getxxyyy_r() functions if available. * libmisc/limits.c, libmisc/entry.c, libmisc/chowntty.c, libmisc/addgrps.c, libmisc/myname.c, libmisc/rlogin.c, libmisc/pwdcheck.c, src/newgrp.c, src/login_nopam.c, src/userdel.c, src/lastlog.c, src/grpck.c, src/gpasswd.c, src/newusers.c, src/chpasswd.c, src/chfn.c, src/groupmems.c, src/usermod.c, src/expiry.c, src/groupdel.c, src/chgpasswd.c, src/su.c, src/useradd.c, src/groupmod.c, src/passwd.c, src/pwck.c, src/groupadd.c, src/chage.c, src/login.c, src/suauth.c, src/faillog.c, src/groups.c, src/chsh.c, src/id.c: Review all the usage of one of the getpwnam(), getpwuid(), getgrnam(), getgrgid(), and getspnam() functions. It was noticed on http://bugs.debian.org/341230 that chfn and chsh use a passwd structure after calling a pam function, which result in using information from the passwd structure requested by pam, not the original one. It is much easier to use the new xget... functions to avoid these issues. I've checked which call to the original get... functions could be left (reducing the scope of the structure if possible), and I've left comments to ease future reviews (e.g. /* local, no need for xgetpwnam */). Note: the getpwent/getgrent calls should probably be checked also. * src/groupdel.c, src/expiry.c: Fix typos in comments. * src/groupmod.c: Re-indent. * libmisc/Makefile.am, lib/groupmem.c, lib/groupio.c, lib/pwmem.c, lib/pwio.c, lib/shadowmem.c, lib/shadowio.c: Move the __<xx>_dup functions (used by the xget... functions) from the <xx>io.c files to the new <xx>mem.c files. This avoid linking some utils against the SELinux library.
2007-11-19 04:45:26 +05:30
/* !USE_PAM, no need for xgetpwnam */
struct passwd *pwd = getpwnam (name);
* lib/prototypes.h, configure.in, libmisc/Makefile.am, libmisc/xgetXXbyYY.c, libmisc/xgetpwnam.c, libmisc/xgetpwuid.c, libmisc/xgetgrnam.c, libmisc/xgetgrgid.c, libmisc/xgetspnam.c: Added functions xgetpwnam(), xgetpwuid(), xgetgrnam(), xgetgrgid(), and xgetspnam(). They allocate memory for the returned structure and are more robust to successive calls. They are implemented with the libc's getxxyyy_r() functions if available. * libmisc/limits.c, libmisc/entry.c, libmisc/chowntty.c, libmisc/addgrps.c, libmisc/myname.c, libmisc/rlogin.c, libmisc/pwdcheck.c, src/newgrp.c, src/login_nopam.c, src/userdel.c, src/lastlog.c, src/grpck.c, src/gpasswd.c, src/newusers.c, src/chpasswd.c, src/chfn.c, src/groupmems.c, src/usermod.c, src/expiry.c, src/groupdel.c, src/chgpasswd.c, src/su.c, src/useradd.c, src/groupmod.c, src/passwd.c, src/pwck.c, src/groupadd.c, src/chage.c, src/login.c, src/suauth.c, src/faillog.c, src/groups.c, src/chsh.c, src/id.c: Review all the usage of one of the getpwnam(), getpwuid(), getgrnam(), getgrgid(), and getspnam() functions. It was noticed on http://bugs.debian.org/341230 that chfn and chsh use a passwd structure after calling a pam function, which result in using information from the passwd structure requested by pam, not the original one. It is much easier to use the new xget... functions to avoid these issues. I've checked which call to the original get... functions could be left (reducing the scope of the structure if possible), and I've left comments to ease future reviews (e.g. /* local, no need for xgetpwnam */). Note: the getpwent/getgrent calls should probably be checked also. * src/groupdel.c, src/expiry.c: Fix typos in comments. * src/groupmod.c: Re-indent. * libmisc/Makefile.am, lib/groupmem.c, lib/groupio.c, lib/pwmem.c, lib/pwio.c, lib/shadowmem.c, lib/shadowio.c: Move the __<xx>_dup functions (used by the xget... functions) from the <xx>io.c files to the new <xx>mem.c files. This avoid linking some utils against the SELinux library.
2007-11-19 04:45:26 +05:30
/* !USE_PAM, no need for xgetspnam */
spwd = getspnam (name);
if (NULL != pwd) {
pwent = *pwd;
}
}
}
/*
* Check to see if the account permits "su". root gets to ignore any
* restricted accounts, but normal users can't become a user if
* there is a "SU" entry in the /etc/porttime file denying access to
* the account.
*/
if (!amroot) {
if (!isttytime (pwent.pw_name, "SU", time ((time_t *) 0))) {
SYSLOG (((0 != pwent.pw_uid) ? LOG_WARN : LOG_CRIT,
"SU by %s to restricted account %s",
oldname, name));
su_failure (tty);
}
}
#endif /* !USE_PAM */
(void) signal (SIGINT, SIG_DFL);
(void) signal (SIGQUIT, SIG_DFL);
cp = getdef_str ((pwent.pw_uid == 0) ? "ENV_SUPATH" : "ENV_PATH");
if (NULL == cp) {
addenv ("PATH=/bin:/usr/bin", NULL);
} else if (strchr (cp, '=') != NULL) {
addenv (cp, NULL);
} else {
addenv ("PATH", cp);
}
if (getenv ("IFS") != NULL) { /* don't export user IFS ... */
addenv ("IFS= \t\n", NULL); /* ... instead, set a safe IFS */
}
/*
* Even if --shell is specified, the subsystem login test is based on
* the shell specified in /etc/passwd (not the one specified with
* --shell, which will be the one executed in the chroot later).
*/
if ('*' == pwent.pw_shell[0]) { /* subsystem root required */
pwent.pw_shell++; /* skip the '*' */
subsystem (&pwent); /* figure out what to execute */
endpwent ();
endspent ();
goto top;
}
sulog (tty, true, oldname, name); /* save SU information */
endpwent ();
endspent ();
#ifdef USE_SYSLOG
if (getdef_bool ("SYSLOG_SU_ENAB")) {
SYSLOG ((LOG_INFO, "+ %s %s:%s", tty,
('\0' != oldname[0]) ? oldname : "???",
('\0' != name[0]) ? name : "???"));
}
#endif
#ifdef USE_PAM
/* set primary group id and supplementary groups */
if (setup_groups (&pwent) != 0) {
pam_end (pamh, PAM_ABORT);
exit (1);
}
/*
* pam_setcred() may do things like resource limits, console groups,
* and much more, depending on the configured modules
*/
ret = pam_setcred (pamh, PAM_ESTABLISH_CRED);
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_setcred: %s", pam_strerror (pamh, ret)));
fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
(void) pam_end (pamh, ret);
exit (1);
}
ret = pam_open_session (pamh, 0);
if (PAM_SUCCESS != ret) {
SYSLOG ((LOG_ERR, "pam_open_session: %s",
pam_strerror (pamh, ret)));
fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
pam_setcred (pamh, PAM_DELETE_CRED);
(void) pam_end (pamh, ret);
exit (1);
}
if (change_environment) {
/* we need to setup the environment *after* pam_open_session(),
* else the UID is changed before stuff like pam_xauth could
* run, and we cannot access /etc/shadow and co
*/
environ = newenvp; /* make new environment active */
/* update environment with all pam set variables */
envcp = pam_getenvlist (pamh);
if (NULL != envcp) {
while (NULL != *envcp) {
addenv (*envcp, NULL);
envcp++;
}
}
}
/* become the new user */
if (change_uid (&pwent) != 0) {
pam_close_session (pamh, 0);
pam_setcred (pamh, PAM_DELETE_CRED);
(void) pam_end (pamh, PAM_ABORT);
exit (1);
}
#else /* !USE_PAM */
environ = newenvp; /* make new environment active */
/* no limits if su from root (unless su must fake login's behavior) */
if (!amroot || fakelogin) {
setup_limits (&pwent);
}
if (setup_uid_gid (&pwent, is_console) != 0) {
exit (1);
}
#endif /* !USE_PAM */
if (change_environment) {
if (fakelogin) {
pwent.pw_shell = shellstr;
setup_env (&pwent);
} else {
addenv ("HOME", pwent.pw_dir);
addenv ("USER", pwent.pw_name);
addenv ("LOGNAME", pwent.pw_name);
addenv ("SHELL", shellstr);
}
}
/*
* This is a workaround for Linux libc bug/feature (?) - the
* /dev/log file descriptor is open without the close-on-exec flag
* and used to be passed to the new shell. There is "fcntl(LogFile,
* F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
* least in 5.4.33). Why? --marekm
*/
closelog ();
/*
* See if the user has extra arguments on the command line. In that
* case they will be provided to the new user's shell as arguments.
*/
if (fakelogin) {
char *arg0;
cp = getdef_str ("SU_NAME");
if (NULL == cp) {
cp = Basename (shellstr);
}
arg0 = xmalloc (strlen (cp) + 2);
arg0[0] = '-';
strcpy (arg0 + 1, cp);
cp = arg0;
} else {
cp = Basename (shellstr);
}
if (!doshell) {
/* Position argv to the remaining arguments */
argv += optind;
if (NULL != command) {
argv -= 2;
argv[0] = "-c";
argv[1] = command;
}
/*
* Use the shell and create an argv
* with the rest of the command line included.
*/
argv[-1] = shellstr;
#ifndef USE_PAM
(void) execve (shellstr, &argv[-1], environ);
err = errno;
(void) fputs (_("No shell\n"), stderr);
SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));
closelog ();
exit ((ENOENT == err) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
#else
run_shell (shellstr, &argv[-1], false, environ); /* no return */
#endif
}
#ifndef USE_PAM
err = shell (shellstr, cp, environ);
exit ((ENOENT == err) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
#else
run_shell (shellstr, &cp, true, environ);
#endif
/* NOT REACHED */
exit (1);
}