freebsd-skq/usr.sbin/pw/pw_group.c
bapt 5daa84302c MFC: 272445,272578,273772,273779,273782,273786,273787,273791
Add a test for bug 191427 where pw(8) will go into an infinite loop
Add some tests for modifying groups
When a group is renamed then the group has been invalidated for sure.
In that case get the group information using the new name.

Fix a regression in pw usermod -G list

The user was perperly adding the to different groups from "list" but was not
removed from the other groups it could have belong to.

Do not delete the group wheel when bad argument is passed to pw groupdel -g

Check that the -g argument is actually a number, if not report an error.
This argument is converted without checking with atoi(3) later so without this
check it converts any alpha entries into 0 meaning it deletes the group wheel

Ensure pw userdel -u <invalid> do not try to remove root

Check the uid passed is actually a number as early as possible

Fix renaming a group via the gr_copy function

Add a regression test to pw(8) because the bug was discovered via using:
pw groupmod

PR:		193704 [1], 185666 [2], 90114 [3], 187189 [4]
Submitted by:	Marc de la Gueronniere [4]
Reported by:	az [1], sub.mesa@gmail.com [2], bkoenig@cs.tu-berlin.de [3],
		mcdouga9@egr.msu.edu [4]
2014-11-04 07:50:48 +00:00

439 lines
11 KiB
C

/*-
* Copyright (C) 1996
* David L. Nugent. 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.
*
* THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT 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 DAVID L. NUGENT 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.
*/
#ifndef lint
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
#include <ctype.h>
#include <err.h>
#include <termios.h>
#include <stdbool.h>
#include <unistd.h>
#include <grp.h>
#include <libutil.h>
#include "pw.h"
#include "bitmap.h"
static struct passwd *lookup_pwent(const char *user);
static void delete_members(char ***members, int *grmembers, int *i,
struct carg *arg, struct group *grp);
static int print_group(struct group * grp, int pretty);
static gid_t gr_gidpolicy(struct userconf * cnf, struct cargs * args);
int
pw_group(struct userconf * cnf, int mode, struct cargs * args)
{
int rc;
struct carg *a_newname = getarg(args, 'l');
struct carg *a_name = getarg(args, 'n');
struct carg *a_gid = getarg(args, 'g');
struct carg *arg;
struct group *grp = NULL;
int grmembers = 0;
char **members = NULL;
static struct group fakegroup =
{
"nogroup",
"*",
-1,
NULL
};
if (a_gid != NULL) {
if (strspn(a_gid->val, "0123456789") != strlen(a_gid->val))
errx(EX_USAGE, "-g expects a number");
}
if (mode == M_LOCK || mode == M_UNLOCK)
errx(EX_USAGE, "'lock' command is not available for groups");
/*
* With M_NEXT, we only need to return the
* next gid to stdout
*/
if (mode == M_NEXT) {
gid_t next = gr_gidpolicy(cnf, args);
if (getarg(args, 'q'))
return next;
printf("%ld\n", (long)next);
return EXIT_SUCCESS;
}
if (mode == M_PRINT && getarg(args, 'a')) {
int pretty = getarg(args, 'P') != NULL;
SETGRENT();
while ((grp = GETGRENT()) != NULL)
print_group(grp, pretty);
ENDGRENT();
return EXIT_SUCCESS;
}
if (a_gid == NULL) {
if (a_name == NULL)
errx(EX_DATAERR, "group name or id required");
if (mode != M_ADD && grp == NULL && isdigit((unsigned char)*a_name->val)) {
(a_gid = a_name)->ch = 'g';
a_name = NULL;
}
}
grp = (a_name != NULL) ? GETGRNAM(a_name->val) : GETGRGID((gid_t) atoi(a_gid->val));
if (mode == M_UPDATE || mode == M_DELETE || mode == M_PRINT) {
if (a_name == NULL && grp == NULL) /* Try harder */
grp = GETGRGID(atoi(a_gid->val));
if (grp == NULL) {
if (mode == M_PRINT && getarg(args, 'F')) {
char *fmems[1];
fmems[0] = NULL;
fakegroup.gr_name = a_name ? a_name->val : "nogroup";
fakegroup.gr_gid = a_gid ? (gid_t) atol(a_gid->val) : -1;
fakegroup.gr_mem = fmems;
return print_group(&fakegroup, getarg(args, 'P') != NULL);
}
errx(EX_DATAERR, "unknown group `%s'", a_name ? a_name->val : a_gid->val);
}
if (a_name == NULL) /* Needed later */
a_name = addarg(args, 'n', grp->gr_name);
/*
* Handle deletions now
*/
if (mode == M_DELETE) {
gid_t gid = grp->gr_gid;
rc = delgrent(grp);
if (rc == -1)
err(EX_IOERR, "group '%s' not available (NIS?)", grp->gr_name);
else if (rc != 0) {
warn("group update");
return EX_IOERR;
}
pw_log(cnf, mode, W_GROUP, "%s(%ld) removed", a_name->val, (long) gid);
return EXIT_SUCCESS;
} else if (mode == M_PRINT)
return print_group(grp, getarg(args, 'P') != NULL);
if (a_gid)
grp->gr_gid = (gid_t) atoi(a_gid->val);
if (a_newname != NULL)
grp->gr_name = pw_checkname((u_char *)a_newname->val, 0);
} else {
if (a_name == NULL) /* Required */
errx(EX_DATAERR, "group name required");
else if (grp != NULL) /* Exists */
errx(EX_DATAERR, "group name `%s' already exists", a_name->val);
extendarray(&members, &grmembers, 200);
members[0] = NULL;
grp = &fakegroup;
grp->gr_name = pw_checkname((u_char *)a_name->val, 0);
grp->gr_passwd = "*";
grp->gr_gid = gr_gidpolicy(cnf, args);
grp->gr_mem = members;
}
/*
* This allows us to set a group password Group passwords is an
* antique idea, rarely used and insecure (no secure database) Should
* be discouraged, but it is apparently still supported by some
* software.
*/
if ((arg = getarg(args, 'h')) != NULL ||
(arg = getarg(args, 'H')) != NULL) {
if (strcmp(arg->val, "-") == 0)
grp->gr_passwd = "*"; /* No access */
else {
int fd = atoi(arg->val);
int precrypt = (arg->ch == 'H');
int b;
int istty = isatty(fd);
struct termios t;
char *p, line[256];
if (istty) {
if (tcgetattr(fd, &t) == -1)
istty = 0;
else {
struct termios n = t;
/* Disable echo */
n.c_lflag &= ~(ECHO);
tcsetattr(fd, TCSANOW, &n);
printf("%sassword for group %s:", (mode == M_UPDATE) ? "New p" : "P", grp->gr_name);
fflush(stdout);
}
}
b = read(fd, line, sizeof(line) - 1);
if (istty) { /* Restore state */
tcsetattr(fd, TCSANOW, &t);
fputc('\n', stdout);
fflush(stdout);
}
if (b < 0) {
warn("-h file descriptor");
return EX_OSERR;
}
line[b] = '\0';
if ((p = strpbrk(line, " \t\r\n")) != NULL)
*p = '\0';
if (!*line)
errx(EX_DATAERR, "empty password read on file descriptor %d", fd);
if (precrypt) {
if (strchr(line, ':') != NULL)
return EX_DATAERR;
grp->gr_passwd = line;
} else
grp->gr_passwd = pw_pwcrypt(line);
}
}
if (((arg = getarg(args, 'M')) != NULL ||
(arg = getarg(args, 'd')) != NULL ||
(arg = getarg(args, 'm')) != NULL) && arg->val) {
int i = 0;
char *p;
struct passwd *pwd;
/* Make sure this is not stay NULL with -M "" */
extendarray(&members, &grmembers, 200);
if (arg->ch == 'd')
delete_members(&members, &grmembers, &i, arg, grp);
else if (arg->ch == 'm') {
int k = 0;
if (grp->gr_mem != NULL) {
while (grp->gr_mem[k] != NULL) {
if (extendarray(&members, &grmembers, i + 2) != -1)
members[i++] = grp->gr_mem[k];
k++;
}
}
}
if (arg->ch != 'd')
for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
int j;
/*
* Check for duplicates
*/
pwd = lookup_pwent(p);
for (j = 0; j < i && strcmp(members[j], pwd->pw_name) != 0; j++)
;
if (j == i && extendarray(&members, &grmembers, i + 2) != -1)
members[i++] = newstr(pwd->pw_name);
}
while (i < grmembers)
members[i++] = NULL;
grp->gr_mem = members;
}
if (getarg(args, 'N') != NULL)
return print_group(grp, getarg(args, 'P') != NULL);
if (mode == M_ADD && (rc = addgrent(grp)) != 0) {
if (rc == -1)
warnx("group '%s' already exists", grp->gr_name);
else
warn("group update");
return EX_IOERR;
} else if (mode == M_UPDATE && (rc = chggrent(a_name->val, grp)) != 0) {
if (rc == -1)
warnx("group '%s' not available (NIS?)", grp->gr_name);
else
warn("group update");
return EX_IOERR;
}
arg = a_newname != NULL ? a_newname : a_name;
/* grp may have been invalidated */
if ((grp = GETGRNAM(arg->val)) == NULL)
errx(EX_SOFTWARE, "group disappeared during update");
pw_log(cnf, mode, W_GROUP, "%s(%ld)", grp->gr_name, (long) grp->gr_gid);
free(members);
return EXIT_SUCCESS;
}
/*
* Lookup a passwd entry using a name or UID.
*/
static struct passwd *
lookup_pwent(const char *user)
{
struct passwd *pwd;
if ((pwd = GETPWNAM(user)) == NULL &&
(!isdigit((unsigned char)*user) ||
(pwd = getpwuid((uid_t) atoi(user))) == NULL))
errx(EX_NOUSER, "user `%s' does not exist", user);
return (pwd);
}
/*
* Delete requested members from a group.
*/
static void
delete_members(char ***members, int *grmembers, int *i, struct carg *arg,
struct group *grp)
{
bool matchFound;
char *user;
char *valueCopy;
char *valuePtr;
int k;
struct passwd *pwd;
if (grp->gr_mem == NULL)
return;
k = 0;
while (grp->gr_mem[k] != NULL) {
matchFound = false;
if ((valueCopy = strdup(arg->val)) == NULL)
errx(EX_UNAVAILABLE, "out of memory");
valuePtr = valueCopy;
while ((user = strsep(&valuePtr, ", \t")) != NULL) {
pwd = lookup_pwent(user);
if (strcmp(grp->gr_mem[k], pwd->pw_name) == 0) {
matchFound = true;
break;
}
}
free(valueCopy);
if (!matchFound &&
extendarray(members, grmembers, *i + 2) != -1)
(*members)[(*i)++] = grp->gr_mem[k];
k++;
}
return;
}
static gid_t
gr_gidpolicy(struct userconf * cnf, struct cargs * args)
{
struct group *grp;
gid_t gid = (gid_t) - 1;
struct carg *a_gid = getarg(args, 'g');
/*
* Check the given gid, if any
*/
if (a_gid != NULL) {
gid = (gid_t) atol(a_gid->val);
if ((grp = GETGRGID(gid)) != NULL && getarg(args, 'o') == NULL)
errx(EX_DATAERR, "gid `%ld' has already been allocated", (long) grp->gr_gid);
} else {
struct bitmap bm;
/*
* We need to allocate the next available gid under one of
* two policies a) Grab the first unused gid b) Grab the
* highest possible unused gid
*/
if (cnf->min_gid >= cnf->max_gid) { /* Sanity claus^H^H^H^Hheck */
cnf->min_gid = 1000;
cnf->max_gid = 32000;
}
bm = bm_alloc(cnf->max_gid - cnf->min_gid + 1);
/*
* Now, let's fill the bitmap from the password file
*/
SETGRENT();
while ((grp = GETGRENT()) != NULL)
if ((gid_t)grp->gr_gid >= (gid_t)cnf->min_gid &&
(gid_t)grp->gr_gid <= (gid_t)cnf->max_gid)
bm_setbit(&bm, grp->gr_gid - cnf->min_gid);
ENDGRENT();
/*
* Then apply the policy, with fallback to reuse if necessary
*/
if (cnf->reuse_gids)
gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
else {
gid = (gid_t) (bm_lastset(&bm) + 1);
if (!bm_isset(&bm, gid))
gid += cnf->min_gid;
else
gid = (gid_t) (bm_firstunset(&bm) + cnf->min_gid);
}
/*
* Another sanity check
*/
if (gid < cnf->min_gid || gid > cnf->max_gid)
errx(EX_SOFTWARE, "unable to allocate a new gid - range fully used");
bm_dealloc(&bm);
}
return gid;
}
static int
print_group(struct group * grp, int pretty)
{
if (!pretty) {
char *buf = NULL;
buf = gr_make(grp);
printf("%s\n", buf);
free(buf);
} else {
int i;
printf("Group Name: %-15s #%lu\n"
" Members: ",
grp->gr_name, (long) grp->gr_gid);
if (grp->gr_mem != NULL) {
for (i = 0; grp->gr_mem[i]; i++)
printf("%s%s", i ? "," : "", grp->gr_mem[i]);
}
fputs("\n\n", stdout);
}
return EXIT_SUCCESS;
}