0d9799de04
free(3) accepts NULL, since the oldest ISO C. I guess the paranoid code was taking care of prehistoric implementations of free(3). I've never known of an implementation that doesn't conform to this, so let's simplify this. Remove xfree(3), which was effectively an equivalent of free(3). Signed-off-by: Alejandro Colomar <alx@kernel.org>
439 lines
9.2 KiB
C
439 lines
9.2 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 1990 - 1994, Julianne Frances Haugh
|
|
* SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
|
|
* SPDX-FileCopyrightText: 2001 , Michał Moskal
|
|
* SPDX-FileCopyrightText: 2005 , Tomasz Kłoczko
|
|
* SPDX-FileCopyrightText: 2007 - 2010, Nicolas François
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#ident "$Id$"
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
|
|
#include "prototypes.h"
|
|
#include "defines.h"
|
|
#include "commonio.h"
|
|
#include "getdef.h"
|
|
#include "groupio.h"
|
|
|
|
static /*@null@*/struct commonio_entry *merge_group_entries (
|
|
/*@null@*/ /*@returned@*/struct commonio_entry *gr1,
|
|
/*@null@*/struct commonio_entry *gr2);
|
|
static int split_groups (unsigned int max_members);
|
|
static int group_open_hook (void);
|
|
|
|
static /*@null@*/ /*@only@*/void *group_dup (const void *ent)
|
|
{
|
|
const struct group *gr = ent;
|
|
|
|
return __gr_dup (gr);
|
|
}
|
|
|
|
static void group_free (/*@out@*/ /*@only@*/void *ent)
|
|
{
|
|
struct group *gr = ent;
|
|
|
|
gr_free (gr);
|
|
}
|
|
|
|
static const char *group_getname (const void *ent)
|
|
{
|
|
const struct group *gr = ent;
|
|
|
|
return gr->gr_name;
|
|
}
|
|
|
|
static void *group_parse (const char *line)
|
|
{
|
|
return (void *) sgetgrent (line);
|
|
}
|
|
|
|
static int group_put (const void *ent, FILE * file)
|
|
{
|
|
const struct group *gr = ent;
|
|
|
|
if ( (NULL == gr)
|
|
|| (valid_field (gr->gr_name, ":\n") == -1)
|
|
|| (valid_field (gr->gr_passwd, ":\n") == -1)
|
|
|| (gr->gr_gid == (gid_t)-1)) {
|
|
return -1;
|
|
}
|
|
|
|
/* FIXME: fail also if gr->gr_mem == NULL ?*/
|
|
if (NULL != gr->gr_mem) {
|
|
size_t i;
|
|
for (i = 0; NULL != gr->gr_mem[i]; i++) {
|
|
if (valid_field (gr->gr_mem[i], ",:\n") == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (putgrent (gr, file) == -1) ? -1 : 0;
|
|
}
|
|
|
|
static int group_close_hook (void)
|
|
{
|
|
unsigned int max_members = getdef_unum("MAX_MEMBERS_PER_GROUP", 0);
|
|
|
|
if (0 == max_members) {
|
|
return 1;
|
|
}
|
|
|
|
return split_groups (max_members);
|
|
}
|
|
|
|
static struct commonio_ops group_ops = {
|
|
group_dup,
|
|
group_free,
|
|
group_getname,
|
|
group_parse,
|
|
group_put,
|
|
fgetsx,
|
|
fputsx,
|
|
group_open_hook,
|
|
group_close_hook
|
|
};
|
|
|
|
static /*@owned@*/struct commonio_db group_db = {
|
|
GROUP_FILE, /* filename */
|
|
&group_ops, /* ops */
|
|
NULL, /* fp */
|
|
#ifdef WITH_SELINUX
|
|
NULL, /* scontext */
|
|
#endif
|
|
0644, /* st_mode */
|
|
0, /* st_uid */
|
|
0, /* st_gid */
|
|
NULL, /* head */
|
|
NULL, /* tail */
|
|
NULL, /* cursor */
|
|
false, /* changed */
|
|
false, /* isopen */
|
|
false, /* locked */
|
|
false, /* readonly */
|
|
false /* setname */
|
|
};
|
|
|
|
int gr_setdbname (const char *filename)
|
|
{
|
|
return commonio_setname (&group_db, filename);
|
|
}
|
|
|
|
/*@observer@*/const char *gr_dbname (void)
|
|
{
|
|
return group_db.filename;
|
|
}
|
|
|
|
int gr_lock (void)
|
|
{
|
|
return commonio_lock (&group_db);
|
|
}
|
|
|
|
int gr_open (int mode)
|
|
{
|
|
return commonio_open (&group_db, mode);
|
|
}
|
|
|
|
/*@observer@*/ /*@null@*/const struct group *gr_locate (const char *name)
|
|
{
|
|
return commonio_locate (&group_db, name);
|
|
}
|
|
|
|
/*@observer@*/ /*@null@*/const struct group *gr_locate_gid (gid_t gid)
|
|
{
|
|
const struct group *grp;
|
|
|
|
gr_rewind ();
|
|
while ( ((grp = gr_next ()) != NULL)
|
|
&& (grp->gr_gid != gid)) {
|
|
}
|
|
|
|
return grp;
|
|
}
|
|
|
|
int gr_update (const struct group *gr)
|
|
{
|
|
return commonio_update (&group_db, (const void *) gr);
|
|
}
|
|
|
|
int gr_remove (const char *name)
|
|
{
|
|
return commonio_remove (&group_db, name);
|
|
}
|
|
|
|
int gr_rewind (void)
|
|
{
|
|
return commonio_rewind (&group_db);
|
|
}
|
|
|
|
/*@observer@*/ /*@null@*/const struct group *gr_next (void)
|
|
{
|
|
return commonio_next (&group_db);
|
|
}
|
|
|
|
int gr_close (void)
|
|
{
|
|
return commonio_close (&group_db);
|
|
}
|
|
|
|
int gr_unlock (void)
|
|
{
|
|
return commonio_unlock (&group_db);
|
|
}
|
|
|
|
void __gr_set_changed (void)
|
|
{
|
|
group_db.changed = true;
|
|
}
|
|
|
|
/*@dependent@*/ /*@null@*/struct commonio_entry *__gr_get_head (void)
|
|
{
|
|
return group_db.head;
|
|
}
|
|
|
|
/*@observer@*/const struct commonio_db *__gr_get_db (void)
|
|
{
|
|
return &group_db;
|
|
}
|
|
|
|
void __gr_del_entry (const struct commonio_entry *ent)
|
|
{
|
|
commonio_del_entry (&group_db, ent);
|
|
}
|
|
|
|
static int gr_cmp (const void *p1, const void *p2)
|
|
{
|
|
gid_t u1, u2;
|
|
|
|
if ((*(struct commonio_entry **) p1)->eptr == NULL) {
|
|
return 1;
|
|
}
|
|
if ((*(struct commonio_entry **) p2)->eptr == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
u1 = ((struct group *) (*(struct commonio_entry **) p1)->eptr)->gr_gid;
|
|
u2 = ((struct group *) (*(struct commonio_entry **) p2)->eptr)->gr_gid;
|
|
|
|
if (u1 < u2) {
|
|
return -1;
|
|
} else if (u1 > u2) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Sort entries by GID */
|
|
int gr_sort ()
|
|
{
|
|
return commonio_sort (&group_db, gr_cmp);
|
|
}
|
|
|
|
static int group_open_hook (void)
|
|
{
|
|
unsigned int max_members = getdef_unum("MAX_MEMBERS_PER_GROUP", 0);
|
|
struct commonio_entry *gr1, *gr2;
|
|
|
|
if (0 == max_members) {
|
|
return 1;
|
|
}
|
|
|
|
for (gr1 = group_db.head; NULL != gr1; gr1 = gr1->next) {
|
|
for (gr2 = gr1->next; NULL != gr2; gr2 = gr2->next) {
|
|
struct group *g1 = (struct group *)gr1->eptr;
|
|
struct group *g2 = (struct group *)gr2->eptr;
|
|
if (NULL != g1 &&
|
|
NULL != g2 &&
|
|
0 == strcmp (g1->gr_name, g2->gr_name) &&
|
|
0 == strcmp (g1->gr_passwd, g2->gr_passwd) &&
|
|
g1->gr_gid == g2->gr_gid) {
|
|
/* Both group entries refer to the same
|
|
* group. It is a split group. Merge the
|
|
* members. */
|
|
gr1 = merge_group_entries (gr1, gr2);
|
|
if (NULL == gr1)
|
|
return 0;
|
|
/* Unlink gr2 */
|
|
if (NULL != gr2->next) {
|
|
gr2->next->prev = gr2->prev;
|
|
}
|
|
/* gr2 does not start with head */
|
|
assert (NULL != gr2->prev);
|
|
gr2->prev->next = gr2->next;
|
|
}
|
|
}
|
|
assert (NULL != gr1);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Merge the list of members of the two group entries.
|
|
*
|
|
* The commonio_entry arguments shall be group entries.
|
|
*
|
|
* You should not merge the members of two groups if they don't have the
|
|
* same name, password and gid.
|
|
*
|
|
* It merge the members of the second entry in the first one, and return
|
|
* the modified first entry on success, or NULL on failure (with errno
|
|
* set).
|
|
*/
|
|
static /*@null@*/struct commonio_entry *merge_group_entries (
|
|
/*@null@*/ /*@returned@*/struct commonio_entry *gr1,
|
|
/*@null@*/struct commonio_entry *gr2)
|
|
{
|
|
struct group *gptr1;
|
|
struct group *gptr2;
|
|
char **new_members;
|
|
size_t members = 0;
|
|
char *new_line;
|
|
size_t new_line_len, i;
|
|
if (NULL == gr2 || NULL == gr1) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
gptr1 = (struct group *)gr1->eptr;
|
|
gptr2 = (struct group *)gr2->eptr;
|
|
if (NULL == gptr2 || NULL == gptr1) {
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
/* Concatenate the 2 lines */
|
|
new_line_len = strlen (gr1->line) + strlen (gr2->line) +1;
|
|
new_line = (char *)malloc (new_line_len + 1);
|
|
if (NULL == new_line) {
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
snprintf(new_line, new_line_len + 1, "%s\n%s", gr1->line, gr2->line);
|
|
|
|
/* Concatenate the 2 list of members */
|
|
for (i=0; NULL != gptr1->gr_mem[i]; i++);
|
|
members += i;
|
|
for (i=0; NULL != gptr2->gr_mem[i]; i++) {
|
|
char **pmember = gptr1->gr_mem;
|
|
while (NULL != *pmember) {
|
|
if (0 == strcmp(*pmember, gptr2->gr_mem[i])) {
|
|
break;
|
|
}
|
|
pmember++;
|
|
}
|
|
if (NULL == *pmember) {
|
|
members++;
|
|
}
|
|
}
|
|
new_members = (char **)calloc ( (members+1), sizeof(char*) );
|
|
if (NULL == new_members) {
|
|
free (new_line);
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
for (i=0; NULL != gptr1->gr_mem[i]; i++) {
|
|
new_members[i] = gptr1->gr_mem[i];
|
|
}
|
|
/* NULL termination enforced by above calloc */
|
|
|
|
members = i;
|
|
for (i=0; NULL != gptr2->gr_mem[i]; i++) {
|
|
char **pmember = new_members;
|
|
while (NULL != *pmember) {
|
|
if (0 == strcmp(*pmember, gptr2->gr_mem[i])) {
|
|
break;
|
|
}
|
|
pmember++;
|
|
}
|
|
if (NULL == *pmember) {
|
|
new_members[members] = gptr2->gr_mem[i];
|
|
members++;
|
|
new_members[members] = NULL;
|
|
}
|
|
}
|
|
|
|
gr1->line = new_line;
|
|
gptr1->gr_mem = new_members;
|
|
|
|
return gr1;
|
|
}
|
|
|
|
/*
|
|
* Scan the group database and split the groups which have more members
|
|
* than specified, if this is the result from a current change.
|
|
*
|
|
* Return 0 on failure (errno set) and 1 on success.
|
|
*/
|
|
static int split_groups (unsigned int max_members)
|
|
{
|
|
struct commonio_entry *gr;
|
|
|
|
for (gr = group_db.head; NULL != gr; gr = gr->next) {
|
|
struct group *gptr = (struct group *)gr->eptr;
|
|
struct commonio_entry *new;
|
|
struct group *new_gptr;
|
|
unsigned int members = 0;
|
|
unsigned int i;
|
|
|
|
/* Check if this group must be split */
|
|
if (!gr->changed) {
|
|
continue;
|
|
}
|
|
if (NULL == gptr) {
|
|
continue;
|
|
}
|
|
for (members = 0; NULL != gptr->gr_mem[members]; members++);
|
|
if (members <= max_members) {
|
|
continue;
|
|
}
|
|
|
|
new = (struct commonio_entry *) malloc (sizeof *new);
|
|
if (NULL == new) {
|
|
errno = ENOMEM;
|
|
return 0;
|
|
}
|
|
new->eptr = group_dup(gr->eptr);
|
|
if (NULL == new->eptr) {
|
|
free (new);
|
|
errno = ENOMEM;
|
|
return 0;
|
|
}
|
|
new_gptr = (struct group *)new->eptr;
|
|
new->line = NULL;
|
|
new->changed = true;
|
|
|
|
/* Enforce the maximum number of members on gptr */
|
|
for (i = max_members; NULL != gptr->gr_mem[i]; i++) {
|
|
free (gptr->gr_mem[i]);
|
|
gptr->gr_mem[i] = NULL;
|
|
}
|
|
/* Shift all the members */
|
|
/* The number of members in new_gptr will be check later */
|
|
for (i = 0; NULL != new_gptr->gr_mem[i + max_members]; i++) {
|
|
free (new_gptr->gr_mem[i]);
|
|
new_gptr->gr_mem[i] = new_gptr->gr_mem[i + max_members];
|
|
new_gptr->gr_mem[i + max_members] = NULL;
|
|
}
|
|
for (; NULL != new_gptr->gr_mem[i]; i++) {
|
|
free (new_gptr->gr_mem[i]);
|
|
new_gptr->gr_mem[i] = NULL;
|
|
}
|
|
|
|
/* insert the new entry in the list */
|
|
new->prev = gr;
|
|
new->next = gr->next;
|
|
gr->next = new;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|