+2007-11-22 Nicolas François <nicolas.francois@centraliens.net>
+
+ * NEWS, lib/getdef.c, man/login.defs.5.xml: New login.defs
+ variable: MAX_MEMBERS_PER_GROUP. Used for the split groups support.
+ * lib/commonio.c, lib/commonio.h: Add an open_hook and close_hook
+ operation. They are called after the database is actually opened
+ and parse, or before it is closed.
+ * lib/groupio.c: Add an open_hook to merge split groups, and an
+ close group to split groups if MAX_MEMBERS_PER_GROUP is set.
+ This fixes gpasswd and chgpasswd when split groups are used.
+ * lib/sgroupio.c, lib/shadowio.c, lib/pwio.c: No open or close
+ hooks for these databases. (unsure about what should be the gshadow
+ behavior for split groups)
+
2007-11-22 Nicolas François <nicolas.francois@centraliens.net>
* NEWS, src/gpasswd.c: Read the group and shadow groups using
extern int putgrent (const struct group *, FILE *);
extern struct group *sgetgrent (const char *);
+static struct commonio_entry *merge_group_entries (struct commonio_entry *,
+ struct commonio_entry *);
+static int split_groups (unsigned int);
+static int group_open_hook (void);
+
static void *group_dup (const void *ent)
{
const struct group *gr = ent;
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_parse,
group_put,
fgetsx,
- fputsx
+ fputsx,
+ group_open_hook,
+ group_close_hook
};
static struct commonio_db group_db = {
{
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; gr1; gr1 = gr1->next) {
+ for (gr2 = gr1->next; 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->prev->next = gr2->next;
+ }
+ }
+ }
+
+ 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 struct commonio_entry *merge_group_entries (struct commonio_entry *gr1,
+ struct commonio_entry *gr2)
+{
+ struct group *gptr1;
+ struct group *gptr2;
+ char *member;
+ char **new_members;
+ int members = 0;
+ char *new_line;
+ int 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) * sizeof(char*));
+ 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 **)malloc ( (members+1) * sizeof(char*) );
+ if (NULL == new_members) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ for (i=0; NULL != gptr1->gr_mem[i]; i++)
+ new_members[i] = gptr1->gr_mem[i];
+ 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];
+ 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; gr; gr = gr->next) {
+ struct group *gptr = (struct group *)gr->eptr;
+ struct commonio_entry *new;
+ struct group *new_gptr;
+ unsigned int members = 0;
+
+ /* 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);
+ new->eptr = group_dup(gr->eptr);
+ if (NULL == new->eptr) {
+ errno = ENOMEM;
+ return 0;
+ }
+ new_gptr = (struct group *)new->eptr;
+ new->line = NULL;
+ new->changed = 1;
+
+ /* Enforce the maximum number of members on gptr */
+ gptr->gr_mem[max_members] = NULL;
+ /* The number of members in new_gptr will be check later */
+ new_gptr->gr_mem = &new_gptr->gr_mem[max_members];
+
+ /* insert the new entry in the list */
+ new->prev = gr;
+ new->next = gr->next;
+ gr->next = new;
+ }
+
+ return 1;
+}
+
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>MAX_MEMBERS_PER_GROUP (number)</term>
+ <listitem>
+ <para>
+ Maximum members per group entry. When the maximum is reached,
+ a new group entry (line) is started is
+ <filename>/etc/group</filename> (with the same name, same
+ password, and same GID).
+ </para>
+ <para>
+ The default value is 0, meaning that there are no limits in
+ the number of members in a group.
+ </para>
+ <!-- Note: on HP, split groups have the same ID, but different
+ names. -->
+ <para>
+ This feature (split group) permits to limit the length of
+ lines in the group file. This is useful to make sure that
+ lines for NIS groups are not larger than 1024 characters.
+ </para>
+ <para>
+ If you need to enforce such limit, you can use 25.
+ </para>
+ <para>
+ Note: split groups may not be supported by all tools (even in
+ the Shadow toolsuite. Yous hould not use this variable unless
+ you really need it.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term>MD5_CRYPT_ENAB (boolean)</term>
<listitem>