Subject: Fixing getgrouplist(3) by adding getgroupmembership(3) API
To: None <>
From: Luke Mewburn <>
List: tech-userlevel
Date: 12/01/2004 21:37:54
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

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.

To recap:

   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
		int		*result
		const char	*name
		gid_t		 basegid
		gid_t		*groups
		gid_t		*ngroups

	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
	nss database).

The problem:

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.

The solution:

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;
	return rv;

(A long explanation for a trivial change.)

Any thoughts / comments / suggested improvements / better solutions ?


Content-Type: application/pgp-signature
Content-Disposition: inline

Version: GnuPG v1.2.6 (NetBSD)