464 lines
11 KiB
C
464 lines
11 KiB
C
/*
|
|
* Copyright (c) 1990 - 1994, Julianne Frances Haugh
|
|
* Copyright (c) 1996 - 2000, Marek Michałkiewicz
|
|
* Copyright (c) 2001 , Michał Moskal
|
|
* Copyright (c) 2005 , Tomasz Kłoczko
|
|
* Copyright (c) 2007 - 2010, 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.
|
|
*/
|
|
|
|
#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 */
|
|
};
|
|
|
|
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, "%s\n%s", gr1->line, gr2->line);
|
|
new_line[new_line_len] = '\0';
|
|
|
|
/* 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++) {
|
|
if (NULL != new_gptr->gr_mem[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;
|
|
}
|
|
|