shadow/src/newgrp.c

712 lines
21 KiB
C
Raw Normal View History

/*
* Copyright 1990 - 1994, Julianne Frances Haugh
* 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. Neither the name of Julianne F. Haugh nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY JULIE HAUGH 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 JULIE HAUGH 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.
*/
#include <config.h>
#ident "$Id$"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include "defines.h"
#include "getdef.h"
#include "prototypes.h"
#include "exitcodes.h"
/*
* Global variables
*/
extern char **newenvp;
extern char **environ;
#ifdef HAVE_SETGROUPS
static int ngroups;
static GETGROUPS_T *grouplist;
#endif
static char *Prog;
static int is_newgrp;
/* local function prototypes */
static void usage (void);
2008-01-02 05:05:55 +05:30
static void check_perms (const struct group *grp,
struct passwd *pwd,
const char *groupname);
static void syslog_sg (const char *name, const char *group);
/*
* usage - print command usage message
*/
static void usage (void)
{
if (is_newgrp)
fputs (_("Usage: newgrp [-] [group]\n"), stderr);
else
fputs (_("Usage: sg group [[-c] command]\n"), stderr);
}
/*
* find_matching_group - search all groups of a given group id for
* membership of a given username
*/
static struct group *find_matching_group (const char *name, gid_t gid)
{
struct group *gr;
char **look;
int notfound = 1;
setgrent ();
while ((gr = getgrent ()) != NULL) {
if (gr->gr_gid != gid) {
continue;
}
/*
* A group with matching GID was found.
* Test for membership of 'name'.
*/
look = gr->gr_mem;
while (*look && (notfound = strcmp (*look++, name)));
if (!notfound)
break;
}
endgrent ();
return gr;
}
2008-01-02 05:05:55 +05:30
/*
* check_perms - check if the user is allowed to switch to this group
*
* If needed, the user will be authenticated.
*
* It will not return if the user could not be authenticated.
*/
static void check_perms (const struct group *grp,
struct passwd *pwd,
const char *groupname)
{
int needspasswd = 0;
struct spwd *spwd;
char *cp;
const char *cpasswd;
/*
* see if she is a member of this group (i.e. in the list of
* members of the group, or if the group is her primary group).
*
* If she isn't a member, she needs to provide the group password.
* If there is no group password, she will be denied access
* anyway.
*
*/
if (grp->gr_gid != pwd->pw_gid && !is_on_list (grp->gr_mem, pwd->pw_name))
needspasswd = 1;
/*
* If she does not have either a shadowed password, or a regular
* password, and the group has a password, she needs to give the
* group password.
*/
spwd = xgetspnam (pwd->pw_name);
if (NULL != spwd)
pwd->pw_passwd = spwd->sp_pwdp;
if (pwd->pw_passwd[0] == '\0' && grp->gr_passwd[0])
needspasswd = 1;
/*
* Now I see about letting her into the group she requested. If she
* is the root user, I'll let her in without having to prompt for
* the password. Otherwise I ask for a password if she flunked one
* of the tests above.
*/
if (getuid () != 0 && needspasswd) {
/*
* get the password from her, and set the salt for
* the decryption from the group file.
*/
cp = getpass (_("Password: "));
if (NULL == cp)
goto failure;
/*
* encrypt the key she gave us using the salt from the
* password in the group file. The result of this encryption
* must match the previously encrypted value in the file.
*/
cpasswd = pw_encrypt (cp, grp->gr_passwd);
strzero (cp);
if (grp->gr_passwd[0] == '\0' ||
strcmp (cpasswd, grp->gr_passwd) != 0) {
SYSLOG ((LOG_INFO,
"Invalid password for group `%s' from `%s'",
groupname, pwd->pw_name));
sleep (1);
fputs (_("Invalid password.\n"), stderr);
2008-01-02 05:05:55 +05:30
goto failure;
}
}
return;
failure:
/* The closelog is probably unnecessary, but it does no
* harm. -- JWP
*/
closelog ();
#ifdef WITH_AUDIT
audit_logger (AUDIT_USER_START, Prog, "changing", NULL, getuid (), 0);
#endif
exit (1);
}
2008-01-02 05:24:51 +05:30
#ifdef USE_SYSLOG
/*
* syslog_sg - log the change of group to syslog
*
* The loggout will also be logged when the user will quit the
* sg/newgrp session.
*/
static void syslog_sg (const char *name, const char *group)
2008-01-02 05:24:51 +05:30
{
const char *loginname = getlogin ();
const char *tty = ttyname (0);
2008-01-02 05:24:51 +05:30
if (loginname != NULL)
loginname = xstrdup (loginname);
if (tty != NULL)
tty = xstrdup (tty);
if (loginname == NULL)
loginname = "???";
if (tty == NULL)
tty = "???";
else if (strncmp (tty, "/dev/", 5) == 0)
tty += 5;
SYSLOG ((LOG_INFO,
"user `%s' (login `%s' on %s) switched to group `%s'",
name, loginname, tty, group));
#ifdef USE_PAM
/*
* We want to fork and exec the new shell in the child, leaving the
* parent waiting to log the session close.
*
* The parent must ignore signals generated from the console
* (SIGINT, SIGQUIT, SIGHUP) which might make the parent terminate
* before its child. When bash is exec'ed as the subshell, it
* generates a new process group id for itself, and consequently
* only SIGHUP, which is sent to all process groups in the session,
* can reach the parent. However, since arbitrary programs can be
* specified as login shells, there is no such guarantee in general.
* For the same reason, we must also ignore stop signals generated
* from the console (SIGTSTP, SIGTTIN, and SIGTTOU) in order to
* avoid any possibility of the parent being stopped when it
* receives SIGCHLD from the terminating subshell. -- JWP
*/
{
pid_t child, pid;
signal (SIGINT, SIG_IGN);
signal (SIGQUIT, SIG_IGN);
signal (SIGHUP, SIG_IGN);
signal (SIGTSTP, SIG_IGN);
signal (SIGTTIN, SIG_IGN);
signal (SIGTTOU, SIG_IGN);
child = fork ();
if (child < 0) {
/* error in fork() */
fprintf (stderr, _("%s: failure forking: %s"),
is_newgrp ? "newgrp" : "sg", strerror (errno));
#ifdef WITH_AUDIT
audit_logger (AUDIT_USER_START, Prog, "changing",
NULL, getuid (), 0);
#endif
exit (1);
} else if (child) {
/* parent - wait for child to finish, then log session close */
int cst = 0;
gid_t gid = getgid();
struct group *grp = getgrgid (gid);
2008-01-02 05:24:51 +05:30
do {
errno = 0;
pid = waitpid (child, &cst, WUNTRACED);
if (pid == child && WIFSTOPPED (cst)) {
/* stop when child stops */
raise (SIGSTOP);
/* wake child when resumed */
kill (child, SIGCONT);
}
} while ((pid == child && WIFSTOPPED (cst)) ||
(pid != child && errno == EINTR));
/* local, no need for xgetgrgid */
if (NULL != grp) {
SYSLOG ((LOG_INFO,
"user `%s' (login `%s' on %s) returned to group `%s'",
name, loginname, tty, grp->gr_name));
} else {
SYSLOG ((LOG_INFO,
"user `%s' (login `%s' on %s) returned to group `%d'",
name, loginname, tty, gid));
/* Either the user's passwd entry has a
* GID that does not match with any group,
* or the group was deleted while the user
* was in a newgrp session.*/
SYSLOG ((LOG_WARN,
"unknown GID `%u' used by user `%s'",
gid, name));
}
2008-01-02 05:24:51 +05:30
closelog ();
exit (0);
}
/* child - restore signals to their default state */
signal (SIGINT, SIG_DFL);
signal (SIGQUIT, SIG_DFL);
signal (SIGHUP, SIG_DFL);
signal (SIGTSTP, SIG_DFL);
signal (SIGTTIN, SIG_DFL);
signal (SIGTTOU, SIG_DFL);
}
#endif /* USE_PAM */
}
#endif /* USE_SYSLOG */
/*
* newgrp - change the invokers current real and effective group id
*/
int main (int argc, char **argv)
{
int initflag = 0;
int i;
int cflag = 0;
int err = 0;
gid_t gid;
char *cp;
2008-01-02 05:05:55 +05:30
const char *name, *prog;
char *group = NULL;
char *command = NULL;
char **envp = environ;
struct passwd *pwd;
struct group *grp;
#ifdef SHADOWGRP
struct sgrp *sgrp;
#endif
#ifdef WITH_AUDIT
audit_help_open ();
#endif
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
/*
* Save my name for error messages and save my real gid incase of
* errors. If there is an error i have to exec a new login shell for
* the user since her old shell won't have fork'd to create the
* process. Skip over the program name to the next command line
* argument.
*
* This historical comment, and the code itself, suggest that the
* behavior of the system/shell on which it was written differed
* significantly from the one I am using. If this process was
* started from a shell (including the login shell), it was fork'ed
* and exec'ed as a child by that shell. In order to get the user
* back to that shell, it is only necessary to exit from this
* process which terminates the child of the fork. The parent shell,
* which is blocked waiting for a signal, will then receive a
* SIGCHLD and will continue; any changes made to the process
* persona or the environment after the fork never occurred in the
* parent process.
*
* Bottom line: we want to save the name and real gid for messages,
* but we do not need to restore the previous process persona and we
* don't need to re-exec anything. -- JWP
*/
Prog = Basename (argv[0]);
is_newgrp = (strcmp (Prog, "newgrp") == 0);
OPENLOG (is_newgrp ? "newgrp" : "sg");
gid = getgid ();
argc--;
argv++;
initenv ();
pwd = get_my_pwent ();
if (!pwd) {
fprintf (stderr, _("unknown UID: %u\n"), getuid ());
#ifdef WITH_AUDIT
audit_logger (AUDIT_USER_START, Prog, "changing", NULL,
getuid (), 0);
#endif
SYSLOG ((LOG_WARN, "unknown UID %u", getuid ()));
closelog ();
exit (1);
}
name = pwd->pw_name;
/*
* Parse the command line. There are two accepted flags. The first
* is "-", which for newgrp means to re-create the entire
* environment as though a login had been performed, and "-c", which
* for sg causes a command string to be executed.
*
* The next argument, if present, must be the new group name. Any
* remaining remaining arguments will be used to execute a command
* as the named group. If the group name isn't present, I just use
* the login group ID of the current user.
*
* The valid syntax are
* newgrp [-] [groupid]
* newgrp [-l] [groupid]
* sg [-]
* sg [-] groupid [[-c command]
*/
if (argc > 0 && (!strcmp (argv[0], "-") || !strcmp (argv[0], "-l"))) {
argc--;
argv++;
initflag = 1;
}
if (!is_newgrp) {
/*
* Do the command line for everything that is
* not "newgrp".
*/
if (argc > 0 && argv[0][0] != '-') {
group = argv[0];
argc--;
argv++;
} else {
usage ();
closelog ();
exit (1);
}
if (argc > 0) {
/*
* skip -c if specified so both forms work:
* "sg group -c command" (as in the man page) or
* "sg group command" (as in the usage message).
*/
if (argc > 1 && strcmp (argv[0], "-c") == 0)
command = argv[1];
else
command = argv[0];
cflag++;
}
} else {
/*
* Do the command line for "newgrp". It's just making sure
* there aren't any flags and getting the new group name.
*/
if (argc > 0 && argv[0][0] == '-') {
usage ();
goto failure;
} else if (argv[0] != (char *) 0) {
group = argv[0];
} else {
/*
* get the group file entry for her login group id.
* the entry must exist, simply to be annoying.
*
* Perhaps in the past, but the default behavior now depends on the
* group entry, so it had better exist. -- JWP
*/
2008-01-02 04:37:55 +05:30
grp = xgetgrgid (pwd->pw_gid);
if (NULL == grp) {
fprintf (stderr, _("unknown GID: %lu\n"),
(unsigned long) pwd->pw_gid);
SYSLOG ((LOG_CRIT, "unknown GID: %lu",
(unsigned long) pwd->pw_gid));
goto failure;
} else
group = grp->gr_name;
}
}
#ifdef HAVE_SETGROUPS
/*
* get the current users groupset. The new group will be added to
* the concurrent groupset if there is room, otherwise you get a
* nasty message but at least your real and effective group id's are
* set.
*/
/* don't use getgroups(0, 0) - it doesn't work on some systems */
i = 16;
for (;;) {
grouplist = (GETGROUPS_T *) xmalloc (i * sizeof (GETGROUPS_T));
ngroups = getgroups (i, grouplist);
if (i > ngroups && !(ngroups == -1 && errno == EINVAL))
break;
/* not enough room, so try allocating a larger buffer */
free (grouplist);
i *= 2;
}
if (ngroups < 0) {
perror ("getgroups");
#ifdef WITH_AUDIT
audit_logger (AUDIT_USER_START, Prog,
"changing", NULL, getuid (), 0);
#endif
exit (1);
}
#endif /* HAVE_SETGROUPS */
/*
* now we put her in the new group. The password file entry for her
* current user id has been gotten. If there was no optional group
* argument she will have her real and effective group id set to the
* set to the value from her password file entry.
*
* If run as newgrp, or as sg with no command, this process exec's
* an interactive subshell with the effective GID of the new group.
* If run as sg with a command, that command is exec'ed in this
* subshell. When this process terminates, either because the user
* exits, or the command completes, the parent of this process
* resumes with the current GID.
*
* If a group is explicitly specified on the command line, the
* interactive shell or command is run with that effective GID.
* Access will be denied if no entry for that group can be found in
* /etc/group. If the current user name appears in the members list
* for that group, access will be granted immediately; if not, the
* user will be challenged for that group's password. If the
* password response is incorrect, if the specified group does not
* have a password, or if that group has been locked by gpasswd -R,
* access will be denied. This is true even if the group specified
* has the user's login GID (as shown in /etc/passwd). If no group
* is explicitly specified on the command line, the effect is
* exactly the same as if a group name matching the user's login GID
* had been explicitly specified. Root, however, is never
* challenged for passwords, and is always allowed access.
*
* The previous behavior was to allow access to the login group if
* no explicit group was specified, irrespective of the group
* control file(s). This behavior is usually not desirable. A user
* wishing to return to the login group has only to exit back to the
* login shell. Generating yet more shell levels in order to
* provide a convenient "return" to the default group has the
* undesirable side effects of confusing the user, scrambling the
* history file, and consuming system resources. The default now is
* to lock out such behavior. A sys admin can allow it by explicitly
* including the user's name in the member list of the user's login
* group. -- JWP
*/
2008-01-02 04:37:55 +05:30
grp = getgrnam (group); /* local, no need for xgetgrnam */
if (NULL == grp) {
fprintf (stderr, _("unknown group: %s\n"), group);
goto failure;
}
/*
* For splitted groups (due to limitations of NIS), check all
* groups of the same GID like the requested group for
* membership of the current user.
*/
grp = find_matching_group (name, grp->gr_gid);
if (!grp) {
/*
* No matching group found. As we already know that
* the group exists, this happens only in the case
* of a requested group where the user is not member.
*
* Re-read the group entry for further processing.
*/
* 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 = xgetgrnam (group);
}
#ifdef SHADOWGRP
2008-01-02 04:37:55 +05:30
sgrp = getsgnam (group);
if (NULL != sgrp) {
grp->gr_passwd = sgrp->sg_passwd;
grp->gr_mem = sgrp->sg_mem;
}
#endif
/*
2008-01-02 05:05:55 +05:30
* Check if the user is allowed to access this group.
*/
2008-01-02 05:05:55 +05:30
check_perms (grp, pwd, group);
/*
* all successful validations pass through this point. The group id
* will be set, and the group added to the concurrent groupset.
*/
#ifdef USE_SYSLOG
if (getdef_bool ("SYSLOG_SG_ENAB")) {
syslog_sg (name, group);
}
#endif /* USE_SYSLOG */
gid = grp->gr_gid;
#ifdef HAVE_SETGROUPS
/*
* I am going to try to add her new group id to her concurrent group
* set. If the group id is already present i'll just skip this part.
* If the group doesn't fit, i'll complain loudly and skip this
* part.
*/
for (i = 0; i < ngroups; i++) {
if (gid == grouplist[i])
break;
}
if (i == ngroups) {
if (ngroups >= sysconf (_SC_NGROUPS_MAX)) {
fputs (_("too many groups\n"), stderr);
} else {
grouplist[ngroups++] = gid;
if (setgroups (ngroups, grouplist)) {
perror ("setgroups");
}
}
}
#endif
/*
* Set the effective GID to the new group id and the effective UID
* to the real UID. For root, this also sets the real GID to the
* new group id.
*/
if (setgid (gid))
perror ("setgid");
if (setuid (getuid ())) {
perror ("setuid");
#ifdef WITH_AUDIT
audit_logger (AUDIT_USER_START, Prog, "changing",
NULL, getuid (), 0);
#endif
exit (1);
}
/*
* See if the "-c" flag was used. If it was, i just create a shell
* command for her using the argument that followed the "-c" flag.
*/
if (cflag) {
closelog ();
execl ("/bin/sh", "sh", "-c", command, (char *) 0);
#ifdef WITH_AUDIT
audit_logger (AUDIT_USER_START, Prog, "changing",
NULL, getuid (), 0);
#endif
perror ("/bin/sh");
exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
}
/*
* I have to get the pathname of her login shell. As a favor, i'll
* try her environment for a $SHELL value first, and then try the
* password file entry. Obviously this shouldn't be in the
* restricted command directory since it could be used to leave the
* restricted environment.
*
* Note that the following assumes this user's entry in /etc/passwd
* does not have a chroot * prefix. If it does, the * will be copied
* verbatim into the exec path. This is probably not an issue
* because if this user is operating in a chroot jail, her entry in
* the version of /etc/passwd that is accessible here should
* probably never have a chroot shell entry (but entries for other
* users might). If I have missed something, and this causes you a
* problem, try using $SHELL as a workaround; also please notify me
* at jparmele@wildbear.com -- JWP
*/
if (!initflag && (cp = getenv ("SHELL")))
prog = cp;
else if (pwd->pw_shell && pwd->pw_shell[0])
prog = pwd->pw_shell;
else
prog = "/bin/sh";
/*
* Now i try to find the basename of the login shell. This will
* become argv[0] of the spawned command.
*/
cp = Basename ((char *) prog);
endspent ();
#ifdef SHADOWGRP
endsgent ();
#endif
endpwent ();
endgrent ();
/*
* Switch back to her home directory if i am doing login
* initialization.
*/
if (initflag) {
if (chdir (pwd->pw_dir))
perror ("chdir");
while (*envp) {
if (strncmp (*envp, "PATH=", 5) == 0 ||
strncmp (*envp, "HOME=", 5) == 0 ||
strncmp (*envp, "SHELL=", 6) == 0 ||
strncmp (*envp, "TERM=", 5) == 0)
addenv (*envp, NULL);
envp++;
}
} else {
while (*envp)
addenv (*envp++, NULL);
}
#ifdef WITH_AUDIT
audit_logger (AUDIT_USER_START, Prog, "changing", NULL, getuid (), 1);
#endif
/*
* Exec the login shell and go away. We are trying to get back to
* the previous environment which should be the user's login shell.
*/
err = shell (prog, initflag ? (char *) 0 : cp, newenvp);
exit (err == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
/* NOTREACHED */
failure:
/*
* The previous code, when run as newgrp, re-exec'ed the shell in
* the current process with the original gid on error conditions.
* See the comment above. This historical behavior now has the
* effect of creating unlogged extraneous shell layers when the
* command line has an error or there is an authentication failure.
* We now just want to exit with error status back to the parent
* process. The closelog is probably unnecessary, but it does no
* harm. -- JWP
*/
closelog ();
#ifdef WITH_AUDIT
audit_logger (AUDIT_USER_START, Prog, "changing", NULL, getuid (), 0);
#endif
exit (1);
}