diff --git a/configure.ac b/configure.ac index 5f1a273377..aeff11d622 100644 --- a/configure.ac +++ b/configure.ac @@ -171,7 +171,7 @@ AC_CHECK_SIZEOF([long]) dnl Availability of various common functions (non-fatal if missing), dnl and various less common threadsafe functions -AC_CHECK_FUNCS_ONCE([cfmakeraw geteuid getgid getgrnam_r getmntent_r \ +AC_CHECK_FUNCS_ONCE([cfmakeraw geteuid getgid getgrnam_r getgrouplist getmntent_r \ getpwuid_r getuid initgroups kill mmap newlocale posix_fallocate \ posix_memalign regexec sched_getaffinity]) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 45cdace24d..24f704736d 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1243,6 +1243,7 @@ virFileWaitForDevices; virFileWriteStr; virFindFileInPath; virGetGroupID; +virGetGroupList; virGetGroupName; virGetHostname; virGetUserDirectory; diff --git a/src/util/util.c b/src/util/util.c index dd4e4eafb2..5ca5034148 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -2575,6 +2575,66 @@ int virGetGroupID(const char *name, } +/* Compute the list of supplementary groups associated with @uid, and + * including @gid in the list (unless it is -1), storing a malloc'd + * result into @list. Return the size of the list on success, or -1 + * on failure with error reported and errno set. May not be called + * between fork and exec. */ +int +virGetGroupList(uid_t uid, gid_t gid, gid_t **list) +{ + int ret = -1; + char *user = NULL; + + *list = NULL; + if (uid == (uid_t)-1) + return 0; + + if (virGetUserEnt(uid, &user, + gid == (gid_t)-1 ? &gid : NULL, NULL) < 0) + return -1; + +# if HAVE_GETGROUPLIST + /* Borrowing from gnulib's LGPLv2+ mgetgroups.c as of July 2013. */ + /* Avoid a bug in older glibc with size 0, by pre-allocating a + * list size and then enlarging if needed. */ + int max = 10; + if (VIR_ALLOC_N(*list, max) < 0) + goto no_memory; + while (1) + { + int ngroups; + int last = max; + + ngroups = getgrouplist(user, gid, *list, &max); + + /* Avoid a bug in Darwin where max is not increased. */ + if (ngroups < 0 && last == max) + max *= 2; + if (VIR_REALLOC_N(*list, max) < 0) { + VIR_FREE(*list); + goto no_memory; + } + if (0 <= ngroups) { + ret = ngroups; + break; + } + } +# else + if (VIR_ALLOC_N(*list, 1) < 0) + goto no_memory; + (*list)[0] = gid; + ret = 1; +# endif + +cleanup: + VIR_FREE(user); + return ret; +no_memory: + virReportOOMError(); + goto cleanup; +} + /* Set the real and effective uid and gid to the given values, and call * initgroups so that the process has all the assumed group membership of * that uid. return 0 on success, -1 on failure (the original system error diff --git a/src/util/util.h b/src/util/util.h index 5ab36ed35c..98964b298f 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -261,6 +261,8 @@ char *virGetUserCacheDirectory(void); char *virGetUserRuntimeDirectory(void); char *virGetUserName(uid_t uid); char *virGetGroupName(gid_t gid); +int virGetGroupList(uid_t uid, gid_t group, gid_t **groups) + ATTRIBUTE_NONNULL(3); int virGetUserID(const char *name, uid_t *uid) ATTRIBUTE_RETURN_CHECK; int virGetGroupID(const char *name,