diff --git a/ChangeLog b/ChangeLog index dc9674f5..3c9a5735 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2007-11-22 Nicolas François + + * 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 * NEWS, src/gpasswd.c: Read the group and shadow groups using diff --git a/NEWS b/NEWS index 5f2fbfc7..818badb7 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,10 @@ shadow-4.0.18.1 -> shadow-4.0.18.2 UNRELEASED - gpasswd: Only read information from the local file group database. It writes the changes in /etc/group and/or /etc/gshadow, but used to read information from getgrnam (hence possibly from another group database). +- New login.defs variable: MAX_MEMBERS_PER_GROUP. It should provide a + better support for split groups. Be careful when using this variable: + not all tools support well split groups (in or out of the shadow + tool suite). It fixes gpasswd and chgpasswd when split groups are used. *** documentation: - Generate the translated manpages from PO at build time. diff --git a/lib/commonio.c b/lib/commonio.c index a1e7e1ef..50197f97 100644 --- a/lib/commonio.c +++ b/lib/commonio.c @@ -520,6 +520,9 @@ int commonio_open (struct commonio_db *db, int mode) if (ferror (db->fp)) goto cleanup_errno; + if (db->ops->open_hook && !db->ops->open_hook ()) + goto cleanup_errno; + db->isopen = 1; return 1; @@ -669,6 +672,9 @@ int commonio_close (struct commonio_db *db) goto success; } + if (db->ops->close_hook && !db->ops->close_hook ()) + goto fail; + memzero (&sb, sizeof sb); if (db->fp) { if (fstat (fileno (db->fp), &sb)) { diff --git a/lib/commonio.h b/lib/commonio.h index 7786d661..b5dfc749 100644 --- a/lib/commonio.h +++ b/lib/commonio.h @@ -52,6 +52,15 @@ struct commonio_ops { */ char *(*fgets) (char *, int, FILE *); int (*fputs) (const char *, FILE *); + + /* + * open_hook and close_hook. + * If non NULL, these functions will be called after the database + * is open or before it is closed. + * They return 0 on failure and 1 on success. + */ + int (*open_hook) (void); + int (*close_hook) (void); }; /* diff --git a/lib/getdef.c b/lib/getdef.c index df474c81..02f2ecaf 100644 --- a/lib/getdef.c +++ b/lib/getdef.c @@ -68,6 +68,7 @@ static struct itemdef def_table[] = { {"LOG_UNKFAIL_ENAB", NULL}, {"MAIL_DIR", NULL}, {"MAIL_FILE", NULL}, + {"MAX_MEMBERS_PER_GROUP", NULL}, {"MD5_CRYPT_ENAB", NULL}, {"PASS_MAX_DAYS", NULL}, {"PASS_MIN_DAYS", NULL}, diff --git a/lib/groupio.c b/lib/groupio.c index 686dca95..8ac673d8 100644 --- a/lib/groupio.c +++ b/lib/groupio.c @@ -10,6 +10,11 @@ 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; @@ -49,6 +54,16 @@ static int group_put (const void *ent, FILE * file) 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, @@ -56,7 +71,9 @@ static struct commonio_ops group_ops = { group_parse, group_put, fgetsx, - fputsx + fputsx, + group_open_hook, + group_close_hook }; static struct commonio_db group_db = { @@ -170,3 +187,171 @@ 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; 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; +} + diff --git a/lib/pwio.c b/lib/pwio.c index ae99b411..cbedb04b 100644 --- a/lib/pwio.c +++ b/lib/pwio.c @@ -57,7 +57,9 @@ static struct commonio_ops passwd_ops = { passwd_parse, passwd_put, fgets, - fputs + fputs, + NULL, /* open_hook */ + NULL /* close_hook */ }; static struct commonio_db passwd_db = { diff --git a/lib/sgroupio.c b/lib/sgroupio.c index cf3f2dd1..dad92f4c 100644 --- a/lib/sgroupio.c +++ b/lib/sgroupio.c @@ -100,7 +100,9 @@ static struct commonio_ops gshadow_ops = { gshadow_parse, gshadow_put, fgetsx, - fputsx + fputsx, + NULL, /* open_hook */ + NULL /* close_hook */ }; static struct commonio_db gshadow_db = { diff --git a/lib/shadowio.c b/lib/shadowio.c index d22f0c6e..e83e8ce1 100644 --- a/lib/shadowio.c +++ b/lib/shadowio.c @@ -54,7 +54,9 @@ static struct commonio_ops shadow_ops = { shadow_parse, shadow_put, fgets, - fputs + fputs, + NULL, /* open_hook */ + NULL /* close_hook */ }; static struct commonio_db shadow_db = { diff --git a/man/login.defs.5.xml b/man/login.defs.5.xml index 9631b99f..a9804e77 100644 --- a/man/login.defs.5.xml +++ b/man/login.defs.5.xml @@ -121,6 +121,36 @@ + + MAX_MEMBERS_PER_GROUP (number) + + + Maximum members per group entry. When the maximum is reached, + a new group entry (line) is started is + /etc/group (with the same name, same + password, and same GID). + + + The default value is 0, meaning that there are no limits in + the number of members in a group. + + + + 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. + + + If you need to enforce such limit, you can use 25. + + + 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. + + + MD5_CRYPT_ENAB (boolean)