changeset 20184:227baaa00ea6

7024 Add getgrouplist() to illumos Reviewed by: Robert Mustacchi <rm@fingolfin.org> Approved by: Dan McDonald <danmcd@joyent.com>
author Jason King <jason.king@joyent.com>
date Thu, 19 Nov 2020 09:41:34 -0600
parents e0d07b298524
children d3b17b1cf245
files usr/src/head/grp.h usr/src/lib/libc/port/gen/initgroups.c usr/src/lib/libc/port/mapfile-vers usr/src/man/man3c/Makefile usr/src/man/man3c/getgrouplist.3c usr/src/pkg/manifests/system-library.man3c.inc
diffstat 6 files changed, 346 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/head/grp.h	Wed Oct 07 11:58:13 2020 +0100
+++ b/usr/src/head/grp.h	Thu Nov 19 09:41:34 2020 -0600
@@ -28,6 +28,8 @@
  *
  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ *
+ * Copyright 2020 Joyent, Inc.
  */
 
 #ifndef _GRP_H
@@ -62,6 +64,7 @@
 
 extern struct group *fgetgrent(FILE *);		/* MT-unsafe */
 extern int initgroups(const char *, gid_t);
+extern int getgrouplist(const char *, gid_t, gid_t *, int *);
 #endif /* defined(__EXTENSIONS__) || !defined(__XOPEN_OR_POSIX) */
 
 #if defined(__EXTENSIONS__) || !defined(__XOPEN_OR_POSIX) || defined(_XPG4_2)
@@ -125,13 +128,6 @@
 extern int __posix_getgrnam_r(const char *, struct group *, char *, size_t,
     struct group **);
 
-#ifdef __lint
-
-#define	getgrgid_r __posix_getgrgid_r
-#define	getgrnam_r __posix_getgrnam_r
-
-#else	/* !__lint */
-
 static int
 getgrgid_r(gid_t __gid, struct group *__grp, char *__buf, size_t __len,
     struct group **__res)
@@ -145,7 +141,6 @@
 	return (__posix_getgrnam_r(__cb, __grp, __buf, __len, __res));
 }
 
-#endif /* !__lint */
 #endif /* __PRAGMA_REDEFINE_EXTNAME */
 
 #else  /* (_POSIX_C_SOURCE - 0 >= 199506L) || ... */
--- a/usr/src/lib/libc/port/gen/initgroups.c	Wed Oct 07 11:58:13 2020 +0100
+++ b/usr/src/lib/libc/port/gen/initgroups.c	Thu Nov 19 09:41:34 2020 -0600
@@ -22,19 +22,20 @@
 /*
  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ * Copyright 2020 Joyent, Inc.
  */
 
 /*	Copyright (c) 1988 AT&T	*/
 /*	  All Rights Reserved  	*/
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #pragma weak _initgroups = initgroups
 
-#include "lint.h"
 #include <stdlib.h>
+#include <string.h>
 #include <errno.h>
 #include <grp.h>
+#include <limits.h>
+#include <sys/debug.h>
 #include <sys/types.h>
 #include <sys/param.h>
 #include <unistd.h>
@@ -84,3 +85,125 @@
 	errno = errsave;
 	return (retsave);
 }
+
+int
+getgrouplist(const char *uname, gid_t agroup, gid_t *groups, int *ngroups)
+{
+	gid_t *grouplist = NULL;
+	gid_t *grpptr;
+	long ngroups_max;
+	int sz, ret;
+
+	/*
+	 * We require sysconf(_SC_NGROUPS_MAX) either returns a sane value (>0)
+	 * or fails. If it returns 0, something has gone horribly, horribly
+	 * wrong.
+	 */
+	ngroups_max = sysconf(_SC_NGROUPS_MAX);
+	if (ngroups_max > INT_MAX)
+		ngroups_max = INT_MAX;
+	else if (ngroups_max < 0)
+		return (-1);
+	VERIFY3S(ngroups_max, >, 0);
+
+	/*
+	 * The documented behavior of getgrouplist(3C) on other platforms
+	 * (e.g. Linux and FreeBSD) do not list any failures other than
+	 * 'groups is too small'. However, examination of some popular
+	 * implementations of getgrouplist on those platforms (e.g. glibc and
+	 * musl -- both appear to share the same man page for getgrouplist(3))
+	 * show that they can in fact fail for other reasons (e.g. ENOMEM,
+	 * EIO).
+	 *
+	 * As such, we don't attempt to catch and deal with any underlying
+	 * errors here. Instead, any underlying errors cause getgrouplist(3C)
+	 * to fail, and any errno value set is left unmodified for examination
+	 * by the caller.
+	 *
+	 * One small complication is that the internal _getgroupsbymember()
+	 * itself doesn't provide any way to report back if the buffer supplied
+	 * to _getgroupsbymember() is too small. Instead, we always supply
+	 * a buffer large enough to hold _SC_NGROUPS_MAX entries -- either
+	 * by allocating one ourselves or using the user supplied buffer if
+	 * sufficiently large.
+	 *
+	 * The system behavior is undefined for any user in more groups than
+	 * _SC_NGROUPS_MAX -- initgroups(3C) for example just ignores any
+	 * excess groups (and which _SC_NGROUPS_MAX sized subset of groups end
+	 * up being set as the secondary groups is non-deterministic), so this
+	 * seems reasonable. Modifying _getgroupsbymember() would require
+	 * modification of the NSS code (due to the pervasive special handling
+	 * of _getgroupsbymember() in the NSS code) as well as modification of
+	 * all NSS backends that implement it. As there are at least a few
+	 * known third party NSS backends, we've opted to avoid doing this
+	 * for now.
+	 */
+
+	if ((ngroups == NULL) || (*ngroups <= 0) || (groups == NULL)) {
+		*ngroups = ngroups_max;
+		errno = EINVAL;
+		return (-1);
+	}
+
+	if (*ngroups < ngroups_max) {
+		/*
+		 * The caller's buffer might be too small, try to use our own
+		 * buffer instead.
+		 */
+		grouplist = calloc(ngroups_max, sizeof (gid_t));
+		if (grouplist == NULL)
+			return (-1);
+
+		grpptr = grouplist;
+		sz = ngroups_max;
+	} else {
+		/* The caller's buffer is large enough, so use it */
+		grpptr = groups;
+		sz = *ngroups;
+	}
+
+	/*
+	 * Always add agroup as the first member -- it should always appear
+	 * in the resulting list of groups, and this allows the backends to
+	 * skip adding it.
+	 */
+	grpptr[0] = agroup;
+
+	ret = _getgroupsbymember(uname, grpptr, sz, 1);
+
+	/*
+	 * We passed in 1 group entry. We should at minimum get 1 entry back
+	 * from _getgroupsbymember(). If we don't, there is a bug in the NSS
+	 * code or a backend. Since the return value is used to size a copy
+	 * further below, we hard fail (abort) here if we get back an
+	 * impossible value so we're not traipsing all over memory (which would
+	 * just make debugging any such problem all the more difficult).
+	 */
+	VERIFY3S(ret, >, 0);
+
+	/*
+	 * If we used the caller's buffer, it means its size was >= ngroups_max
+	 * entries, and we're done.
+	 */
+	if (grpptr == groups) {
+		/* Set *ngroups to the number of entries in groups */
+		*ngroups = ret;
+		return (ret);
+	}
+
+	/* We verified earlier *ngroups > 0 */
+	if (ret < *ngroups) {
+		/* Copy as many gids that will fit */
+		(void) memcpy(groups, grpptr, *ngroups * sizeof (gid_t));
+
+		*ngroups = ret;
+		ret = -1;
+		errno = ERANGE;
+	} else {
+		(void) memcpy(groups, grpptr, ret * sizeof (gid_t));
+		*ngroups = ret;
+	}
+
+	free(grouplist);
+	return (ret);
+}
--- a/usr/src/lib/libc/port/mapfile-vers	Wed Oct 07 11:58:13 2020 +0100
+++ b/usr/src/lib/libc/port/mapfile-vers	Thu Nov 19 09:41:34 2020 -0600
@@ -23,7 +23,7 @@
 # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 # Copyright 2018 Nexenta Systems, Inc.
 # Copyright (c) 2012 by Delphix. All rights reserved.
-# Copyright 2018 Joyent, Inc.
+# Copyright 2020 Joyent, Inc.
 # Copyright (c) 2013, OmniTI Computer Consulting, Inc. All rights reserved.
 # Copyright (c) 2013 Gary Mills
 # Copyright 2014 Garrett D'Amore <garrett@damore.org>
@@ -78,6 +78,11 @@
 $add amd64
 $endif
 
+SYMBOL_VERSION ILLUMOS_0.38 {
+    protected:
+	getgrouplist;
+} ILLUMOS_0.37;
+
 SYMBOL_VERSION ILLUMOS_0.37 {
    global:
 	__stack_chk_guard;
--- a/usr/src/man/man3c/Makefile	Wed Oct 07 11:58:13 2020 +0100
+++ b/usr/src/man/man3c/Makefile	Thu Nov 19 09:41:34 2020 -0600
@@ -14,7 +14,7 @@
 # Copyright 2018 Nexenta Systems, Inc.
 # Copyright 2013, OmniTI Computer Consulting, Inc. All rights reserved.
 # Copyright 2014 Garrett D'Amore <garrett@damore.org>
-# Copyright 2018 Joyent, Inc.
+# Copyright 2020 Joyent, Inc.
 # Copyright 2018 Jason King
 # Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
 #
@@ -172,6 +172,7 @@
 		getenv.3c					\
 		getexecname.3c					\
 		getgrnam.3c					\
+		getgrouplist.3c					\
 		gethostid.3c					\
 		gethostname.3c					\
 		gethrtime.3c					\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/man/man3c/getgrouplist.3c	Thu Nov 19 09:41:34 2020 -0600
@@ -0,0 +1,207 @@
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source.  A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2020 Joyent, Inc.
+.\"
+.Dd November 22, 2020
+.Dt GETGROUPLIST 3C
+.Os
+.Sh NAME
+.Nm getgrouplist
+.Nd calculate group access list
+.Sh SYNOPSIS
+.In grp.h
+.Ft int
+.Fo getgrouplist
+.Fa "const char *user"
+.Fa "gid_t agroup"
+.Fa "gid_t *groups"
+.Fa "int *ngroups"
+.Fc
+.Sh DESCRIPTION
+The
+.Fn getgrouplist
+function queries the group database to obtain the list of groups that
+.Fa user
+belongs to.
+The
+.Fa agroup
+group is always added to the resulting group list.
+This value is typically the primary gid of the user from the
+.Sy passwd
+database.
+.Pp
+When calling
+.Fn getgrouplist ,
+the caller should set the maximum number of groups that
+.Fa groups
+can hold in
+.Fa *ngroups .
+The value of
+.Dv NGROUPS_MAX
+can be used to size
+.Fa groups
+to ensure it can hold any number of groups supported by the system.
+.Pp
+Upon return,
+.Fn getgrouplist
+stores the list of groups that
+.Fa user
+belongs to in
+.Fa groups
+and stores the number of groups
+.Fa user
+belongs to in
+.Fa *ngroups
+.Po
+this may be a smaller than the value passed in when
+calling
+.Fn getgrouplist
+.Pc .
+If
+.Fa groups
+is too small to hold all of the groups
+.Fa user
+belongs to,
+.Fn getgrouplist
+fails and sets
+.Fa *ngroups
+to a value large enough to hold the full result.
+.Sh RETURN VALUES
+On success,
+.Fn getgrouplist
+returns the number of groups
+.Fa user
+belongs to, fills in
+.Fa groups
+with the gids of the groups
+.Fa user
+belongs to, and also sets
+.Fa *ngroups
+to the number of groups
+.Fa user
+belongs to.
+.Pp
+On failure,
+.Fn getgrouplist
+returns -1 and
+.Va errno
+is set.
+.Pp
+The behavior of
+.Fn getgrouplist
+is undefined if the total number of groups a user belongs to exceeds
+.Dv NGROUPS_MAX .
+.Pp
+Note that on
+.Fx ,
+.Fn getgrouplist
+always returns -1 on failure or 0 on success.
+A caller must rely on the value set in
+.Fa *ngroups
+upon return to determine the number of entries in
+.Fa groups .
+.Pp
+On Linux, both glibc and musl return the number of groups
+.Fa user
+belongs to on success and returns -1 on failure.
+.Pp
+None of these other implementations document any
+.Va errno
+values on failure, however their implementations show that
+.Va errno
+may be set on failure.
+Software using
+.Fn getgrouplist
+should be aware of these differences when attemping to write portable
+software.
+.Sh EXAMPLES
+.Sy Example 1
+Print all the groups for a user.
+.Bd -literal
+#include <pwd.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <err.h>
+
+void
+printgroups(const char *user)
+{
+    struct passwd *pw;
+    gid_t *groups;
+    int ngroups, ret;
+
+    if ((groups = calloc(NGROUPS_MAX, sizeof (gid_t))) == NULL)
+        err(EXIT_FAILURE, "calloc");
+
+    if ((pw = getpwnam(user)) == NULL)
+        err(EXIT_FAILURE, "getpwname");
+
+    ngroups = NGROUPS_MAX;
+    ret = getgrouplist(user, pw->pw_gid, groups, &ngroups);
+    if (ret < 0)
+        err(EXIT_FAILURE, "getgrouplist");
+
+    for (int i = 0; i < ret; i++) {
+        struct group *gr = getgrgid(groups[i]);
+
+        (void) printf("%s ", gr->gr_name);
+    }
+    (void) fputc('\\n', stdout);
+
+    free(groups);
+}
+.Ed
+.Sh ERRORS
+On failure,
+.Fn getgrouplist
+returns -1, and will set errno to one one of the following values:
+.Bl -tag -width Dv
+.It Er ENOMEM
+Not enough memory to complete the request.
+.It Er EINVAL
+One of the parameters is invalid
+.Po
+for example,
+.Fa ngroups
+is
+.Dv NULL
+.Pc .
+.It Dv ERANGE
+The supplied value of
+.Fa *ngroups
+is too small to hold the results.
+.Fa *ngroups
+is set
+.Po
+upon return
+.Pc
+to a value large enough to hold the results, and a partial set of
+results is written to
+.Fa groups .
+The value written to
+.Fa *ngroups
+may be larger than the value returned by a successful call to
+.Fn getgrouplist .
+.El
+.Sh INTERFACE STABILITY
+.Sy Uncommitted
+.Sh MT-LEVEL
+.Sy MT-Safe
+.Sh SEE ALSO
+.Xr groups 1 ,
+.Xr getuid 2 ,
+.Xr getgrnam 3C ,
+.Xr getgroups 3C ,
+.Xr initgroups 3C ,
+.Xr limits.h 3HEAD
--- a/usr/src/pkg/manifests/system-library.man3c.inc	Wed Oct 07 11:58:13 2020 +0100
+++ b/usr/src/pkg/manifests/system-library.man3c.inc	Thu Nov 19 09:41:34 2020 -0600
@@ -15,7 +15,7 @@
 # Copyright 2013 OmniTI Computer Consulting, Inc. All rights reserved.
 # Copyright 2014 Garrett D'Amore <garrett@damore.org>
 # Copyright 2018 Jason King
-# Copyright 2018, Joyent, Inc.
+# Copyright 2020 Joyent, Inc.
 # Copyright 2019 OmniOS Community Edition (OmniOSce) Association.
 #
 
@@ -168,6 +168,7 @@
 file path=usr/share/man/man3c/getenv.3c
 file path=usr/share/man/man3c/getexecname.3c
 file path=usr/share/man/man3c/getgrnam.3c
+file path=usr/share/man/man3c/getgrouplist.3c
 file path=usr/share/man/man3c/gethostid.3c
 file path=usr/share/man/man3c/gethostname.3c
 file path=usr/share/man/man3c/gethrtime.3c