Subject: Fixing getgrouplist(3) by adding getgroupmembership(3) API
To: None <tech-userlevel@NetBSD.org>
From: Luke Mewburn <lukem@NetBSD.org>
Date: 12/01/2004 21:37:54
Content-Type: text/plain; charset=us-ascii
As part of my cleanup of the backend API used by various
nsswitch.conf / nsdispatch(3) users, I noticed that there's
a problem with the getgrouplist(3) implementation.
1. getgrouplist(3) is:
int getgrouplist(const char *name, gid_t basegid,
gid_t *groups, int *ngroups);
It is a 4.4BSD function, and is not in any standard AFAICT.
2. A backend callback method for nsdispatch(3) callers is
int somemethod(void *nsrv, void *nscb, va_list ap);
3. To simplify implementation of new dynamic nss_ modules,
the encoding of the `va_list ap' (in `2.') for a given
front-end (e.g., getgrnam(3), getgrouplist(3)) is
directly derived from the signature of that front-end.
E.g., va_list ap for struct group *getgrnam(const char *) backend
struct group **result
E.g., va_list ap for getgrouplist(3) backend
const char *name
Thus, if you want to write an nss_ldap source implementation
for the passwd & group nsswitch databases, you just need to
provide backend functions for the various "public" APIs, and
the va_list API of a given backend is trivial to determine.
(I do intend to document which APIs are required for a given
Originally getgrouplist(3) was implemented in terms of getgrent(),
and source-specific backends were not required. However, MIT Hesiod
(unlike ULTRIX Hesiod) does not support iterating over the groups
database with getgrent(3). We previously worked around with with a
private _getgrent_user() API, but that doesn't scale for arbitrary
dynamic nss sources, so I rewrote getgrouplist(3) to optionally use
source-specific backends where necessary (e.g., dns_getgrouplist, ...)
However, the API for getgrouplist(3) is not suitable for being called
by multiple backends to "merge" the results. E.g., when using an
nsswitch.conf(5) entry of:
groups: files dns winbind
Whilst the 'ngroups' value contains the size of the 'groups' array on
function entry, it contains the number of matches of exit, which
prevents the next getgroupslist() backend from appending to the result.
After considering various methods to solving this problem, including
"special casing" the backend for getgrouplist(3) by changing its
va_list API, researching into what other systems have provided
(there isn't consistency), and discussing with a few people, I've
decided that the best approach is to provide a new API:
int getgroupmembership(const char *name, gid_t basegid,
gid_t *groups, int maxgrp, int *ngroups);
The difference to getgrouplist(3) is to not overload the "*ngroups"
parameter as both an "IN" parameter (size of *group) and an "OUT"
parameter (number of results). I.e:
maxgrp maximum number of elements supported in *groups
ngroups offset of first unused element in *groups=20
Duplicate gid suppression is still required of a backend,
and a caller can pre-populate part of *groups and set *ngroups
to the first unused element.
When adding a gid, if *ngroups > maxgrp, *groups isn't populated
yet *ngroups is still incremented, to allow the caller to provide
a larger sized *groups array and call again (as per getgrouplist(3)).
This allows multiple backends to be called in sequence, adding to
*groups as necessary.
getgrouplist(3) becomes the following trivial wrapper:
int rv, groupc;
groupc =3D 0;
rv =3D getgroupmembership(uname, agroup, groups, *grpcnt, &groupc);
*grpcnt =3D groupc;
(A long explanation for a trivial change.)
Any thoughts / comments / suggested improvements / better solutions ?
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (NetBSD)
-----END PGP SIGNATURE-----