480 lines
12 KiB
C
480 lines
12 KiB
C
|
/*
|
||
|
* 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>
|
||
|
|
||
|
#include "rcsid.h"
|
||
|
RCSID(PKG_VER "$Id: newgrp.c,v 1.13 1999/07/09 18:02:43 marekm Exp $")
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <errno.h>
|
||
|
#include <grp.h>
|
||
|
#include <pwd.h>
|
||
|
|
||
|
#include "prototypes.h"
|
||
|
#include "defines.h"
|
||
|
|
||
|
#include "getdef.h"
|
||
|
|
||
|
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 P_((void));
|
||
|
int main P_((int, char **));
|
||
|
|
||
|
/*
|
||
|
* usage - print command usage message
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
usage(void)
|
||
|
{
|
||
|
if (is_newgrp)
|
||
|
fprintf (stderr, _("usage: newgrp [ - ] [ group ]\n"));
|
||
|
else
|
||
|
fprintf (stderr, _("usage: sg group [ command ]\n"));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* newgrp - change the invokers current real and effective group id
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
main(int argc, char **argv)
|
||
|
{
|
||
|
int initflag = 0;
|
||
|
int needspasswd = 0;
|
||
|
int i;
|
||
|
int cflag = 0;
|
||
|
gid_t gid;
|
||
|
char *cp;
|
||
|
const char *cpasswd, *name, *prog;
|
||
|
char *group = NULL;
|
||
|
char *command=NULL;
|
||
|
char **envp = environ;
|
||
|
struct passwd *pwd;
|
||
|
struct group *grp;
|
||
|
#ifdef SHADOWPWD
|
||
|
struct spwd *spwd;
|
||
|
#endif
|
||
|
#ifdef SHADOWGRP
|
||
|
struct sgrp *sgrp;
|
||
|
#endif
|
||
|
|
||
|
sanitize_env();
|
||
|
|
||
|
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.
|
||
|
*/
|
||
|
|
||
|
Prog = Basename(argv[0]);
|
||
|
is_newgrp = (strcmp(Prog, "newgrp") == 0);
|
||
|
openlog(is_newgrp ? "newgrp" : "sg", LOG_PID|LOG_CONS|LOG_NOWAIT, LOG_AUTH);
|
||
|
gid = getgid();
|
||
|
argc--; argv++;
|
||
|
|
||
|
initenv();
|
||
|
|
||
|
pwd = get_my_pwent();
|
||
|
if (!pwd) {
|
||
|
fprintf (stderr, _("unknown uid: %d\n"), (int) getuid());
|
||
|
SYSLOG((LOG_WARN, "unknown uid %d\n", (int) 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 [ 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) {
|
||
|
command = argv[1];
|
||
|
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.
|
||
|
*/
|
||
|
|
||
|
if (! (grp = getgrgid (pwd->pw_gid))) {
|
||
|
fprintf(stderr, _("unknown gid: %ld\n"),
|
||
|
(long) pwd->pw_gid);
|
||
|
SYSLOG((LOG_CRIT, "unknown gid: %ld\n",
|
||
|
(long) pwd->pw_gid));
|
||
|
goto failure;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#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);
|
||
|
/* XXX Bug#38672
|
||
|
login: newgrp/sg fails with user in 17 groups */
|
||
|
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");
|
||
|
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 value from her password file entry. otherwise
|
||
|
* we validate her access to the specified group.
|
||
|
*/
|
||
|
|
||
|
if (group == (char *) 0) {
|
||
|
if (! (grp = getgrgid (pwd->pw_gid))) {
|
||
|
fprintf (stderr, _("unknown gid: %d\n"), pwd->pw_gid);
|
||
|
goto failure;
|
||
|
}
|
||
|
group = grp->gr_name;
|
||
|
} else if (! (grp = getgrnam (group))) {
|
||
|
fprintf (stderr, _("unknown group: %s\n"), group);
|
||
|
goto failure;
|
||
|
}
|
||
|
#ifdef SHADOWGRP
|
||
|
if ((sgrp = getsgnam (group))) {
|
||
|
grp->gr_passwd = sgrp->sg_passwd;
|
||
|
grp->gr_mem = sgrp->sg_mem;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* see if she is a member of this 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 (!is_on_list(grp->gr_mem, 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.
|
||
|
*/
|
||
|
|
||
|
#ifdef SHADOWPWD
|
||
|
if ((spwd = getspnam (name)))
|
||
|
pwd->pw_passwd = spwd->sp_pwdp;
|
||
|
#endif
|
||
|
|
||
|
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. note that she
|
||
|
* won't have to provide the password to her login group even
|
||
|
* if she isn't listed as a member.
|
||
|
*/
|
||
|
|
||
|
if (getuid () != 0 && needspasswd) {
|
||
|
|
||
|
/*
|
||
|
* get the password from her, and set the salt for
|
||
|
* the decryption from the group file.
|
||
|
*/
|
||
|
|
||
|
if (! (cp = getpass (_("Password:"))))
|
||
|
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') {
|
||
|
/*
|
||
|
* there is no password, print out "Sorry" and give up
|
||
|
*/
|
||
|
sleep(1);
|
||
|
fputs (_("Sorry.\n"), stderr);
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
if (strcmp (cpasswd, grp->gr_passwd) != 0) {
|
||
|
SYSLOG((LOG_INFO,
|
||
|
"Invalid password for group `%s' from `%s'\n",
|
||
|
group, name));
|
||
|
sleep(1);
|
||
|
fputs (_("Sorry.\n"), stderr);
|
||
|
goto failure;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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((LOG_INFO, "user `%s' switched to group `%s'\n",
|
||
|
name, group));
|
||
|
#endif
|
||
|
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 >= NGROUPS_MAX) {
|
||
|
fprintf (stderr, _("too many groups\n"));
|
||
|
} else {
|
||
|
grouplist[ngroups++] = gid;
|
||
|
if (setgroups(ngroups, grouplist)) {
|
||
|
perror("setgroups");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
okay:
|
||
|
/*
|
||
|
* i set her group id either to the value she requested, or
|
||
|
* to the original value if the newgrp failed.
|
||
|
*/
|
||
|
|
||
|
if (setgid(gid))
|
||
|
perror("setgid");
|
||
|
|
||
|
if (setuid(getuid())) {
|
||
|
perror("setuid");
|
||
|
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);
|
||
|
if (errno == ENOENT) {
|
||
|
perror("/bin/sh");
|
||
|
exit(127);
|
||
|
} else {
|
||
|
perror("/bin/sh");
|
||
|
exit(126);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
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);
|
||
|
|
||
|
#ifdef SHADOWPWD
|
||
|
endspent ();
|
||
|
#endif
|
||
|
#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);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
shell(prog, initflag ? (char *)0 : cp);
|
||
|
/*NOTREACHED*/
|
||
|
|
||
|
failure:
|
||
|
/*
|
||
|
* this is where all failures land. the group id will not
|
||
|
* have been set, so the setgid() below will set me to the
|
||
|
* original group id i had when i was invoked.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* only newgrp needs to re-exec the user's shell. that is
|
||
|
* because the shell doesn't recognize "sg", so it doesn't
|
||
|
* "exec" this command.
|
||
|
*/
|
||
|
|
||
|
if (!is_newgrp) {
|
||
|
closelog();
|
||
|
exit (1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The GID is still set to the old value, so now I can
|
||
|
* give the user back her shell.
|
||
|
*/
|
||
|
|
||
|
goto okay;
|
||
|
}
|