diff --git a/MANIFEST b/MANIFEST index 3c9f5c9a9..a2ed2a957 100644 --- a/MANIFEST +++ b/MANIFEST @@ -25,6 +25,7 @@ compat/dlopen.c compat/fnmatch.c compat/fnmatch.h compat/getcwd.c +compat/getgrouplist.c compat/getline.c compat/getprogname.c compat/glob.c diff --git a/compat/Makefile.in b/compat/Makefile.in index 7a746579c..5d971c682 100644 --- a/compat/Makefile.in +++ b/compat/Makefile.in @@ -134,6 +134,9 @@ fnmatch.lo: $(srcdir)/fnmatch.c $(top_builddir)/config.h $(incdir)/missing.h \ $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/fnmatch.c getcwd.lo: $(srcdir)/getcwd.c $(top_builddir)/config.h $(incdir)/missing.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/getcwd.c +getgrouplist.lo: $(srcdir)/getgrouplist.c $(top_builddir)/config.h \ + $(incdir)/missing.h + $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/getgrouplist.c getline.lo: $(srcdir)/getline.c $(top_builddir)/config.h $(incdir)/missing.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/getline.c getprogname.lo: $(srcdir)/getprogname.c $(top_builddir)/config.h \ diff --git a/compat/getgrouplist.c b/compat/getgrouplist.c index 69064894d..2d22714f4 100644 --- a/compat/getgrouplist.c +++ b/compat/getgrouplist.c @@ -36,6 +36,51 @@ #include "missing.h" +#ifdef HAVE_GETGRSET +/* + * BSD-compatible getgrouplist(3) using getgrset(3) + */ +int +getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp) +{ + char *cp, *grset = NULL; + int i, ngroups = 1; + int grpsize = *ngroupsp; + int rval = -1; + gid_t gid; + + /* We support BSD semantics where the first element is the base gid */ + if (grpsize <= 0) + return -1; + groups[0] = basegid; + +#ifdef HAVE_SETAUTHDB + aix_setauthdb((char *) name); +#endif + if ((grset = getgrset(name)) != NULL) { + for (cp = strtok(grset, ","); cp != NULL; cp = strtok(NULL, ",")) { + gid = atoi(cp); + if (gid != basegid) { + if (ngroups == grpsize) + goto done; + groups[ngroups++] = gid; + } + } + } + rval = 0; + +done: + efree(grset); +#ifdef HAVE_SETAUTHDB + aix_restoreauthdb(); +#endif + *ngroupsp = ngroups; + + return rval; +} + +#else /* HAVE_GETGRSET */ + /* * BSD-compatible getgrouplist(3) using getgrent(3) */ @@ -83,3 +128,4 @@ done: return rval; } +#endif /* HAVE_GETGRSET */ diff --git a/config.h.in b/config.h.in index ec57bfd70..a0c704597 100644 --- a/config.h.in +++ b/config.h.in @@ -153,9 +153,15 @@ /* Define to 1 if you have the `getdomainname' function. */ #undef HAVE_GETDOMAINNAME -/* Define to 1 if you have the `getgroups' function. */ +/* Define to 1 if you have the `getgrouplist' function. */ +#undef HAVE_GETGROUPLIST + +/* Define to 1 if your system has a working `getgroups' function. */ #undef HAVE_GETGROUPS +/* Define to 1 if you have the `getgrset' function. */ +#undef HAVE_GETGRSET + /* Define to 1 if you have the `getifaddrs' function. */ #undef HAVE_GETIFADDRS @@ -214,9 +220,6 @@ /* Define to 1 if contains struct in6_addr. */ #undef HAVE_IN6_ADDR -/* Define to 1 if you have the `initgroups' function. */ -#undef HAVE_INITGROUPS - /* Define to 1 if you have the `initprivs' function. */ #undef HAVE_INITPRIVS diff --git a/configure b/configure index 256aab175..42eea68c8 100755 --- a/configure +++ b/configure @@ -13109,6 +13109,19 @@ fi with_netsvc="/etc/netsvc.conf" fi + # For implementing getgrouplist() + for ac_func in getgrset +do : + ac_fn_c_check_func "$LINENO" "getgrset" "ac_cv_func_getgrset" +if test "x$ac_cv_func_getgrset" = x""yes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_GETGRSET 1 +_ACEOF + +fi +done + + # LDR_PRELOAD is supported in AIX 5.3 and later case "$OSREV" in 1-4.*) with_noexec=no ;; @@ -15538,7 +15551,7 @@ $as_echo "#define HAVE_GETGROUPS 1" >>confdefs.h fi LIBS=$ac_save_LIBS -for ac_func in strrchr sysconf tzset strftime initgroups getgroups fstat \ +for ac_func in strrchr sysconf tzset strftime fstat \ regcomp setlocale nl_langinfo getaddrinfo mbr_check_membership \ setrlimit64 sysctl do : @@ -15553,6 +15566,25 @@ _ACEOF fi done +for ac_func in getgrouplist +do : + ac_fn_c_check_func "$LINENO" "getgrouplist" "ac_cv_func_getgrouplist" +if test "x$ac_cv_func_getgrouplist" = x""yes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_GETGROUPLIST 1 +_ACEOF + +else + case " $LIBOBJS " in + *" $ac_func.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS $ac_func.$ac_objext" + ;; +esac + +fi +done + + for ac_func in getline do : ac_fn_c_check_func "$LINENO" "getline" "ac_cv_func_getline" diff --git a/configure.in b/configure.in index cc5018dd8..e2b460070 100644 --- a/configure.in +++ b/configure.in @@ -1528,6 +1528,9 @@ case "$host" in with_netsvc="/etc/netsvc.conf" fi + # For implementing getgrouplist() + AC_CHECK_FUNCS(getgrset) + # LDR_PRELOAD is supported in AIX 5.3 and later case "$OSREV" in [1-4].*) with_noexec=no ;; @@ -2063,9 +2066,10 @@ dnl dnl Function checks dnl AC_FUNC_GETGROUPS -AC_CHECK_FUNCS(strrchr sysconf tzset strftime initgroups getgroups fstat \ +AC_CHECK_FUNCS(strrchr sysconf tzset strftime fstat \ regcomp setlocale nl_langinfo getaddrinfo mbr_check_membership \ setrlimit64 sysctl) +AC_REPLACE_FUNCS(getgrouplist) AC_CHECK_FUNCS(getline, [], [ AC_LIBOBJ(getline) AC_CHECK_FUNCS(fgetln) diff --git a/include/missing.h b/include/missing.h index 68d5879b8..e7eae3eba 100644 --- a/include/missing.h +++ b/include/missing.h @@ -286,6 +286,9 @@ void closefrom(int); #ifndef HAVE_GETCWD char *getcwd(char *, size_t size); #endif +#ifndef HAVE_GETGROUPLIST +int getgrouplist(const char *, gid_t, gid_t *, int *); +#endif #ifndef HAVE_GETLINE ssize_t getline(char **, size_t *, FILE *); #endif diff --git a/mkdep.pl b/mkdep.pl index 2196dc41b..cc228c28b 100755 --- a/mkdep.pl +++ b/mkdep.pl @@ -53,7 +53,7 @@ sub mkdep { $makefile =~ s:\@SUDOERS_OBJS\@:bsm_audit.lo linux_audit.lo ldap.lo plugin_error.lo:; # XXX - fill in AUTH_OBJS from contents of the auth dir instead $makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo kerb4.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid.lo securid5.lo sia.lo:; - $makefile =~ s:\@LTLIBOBJS\@:closefrom.lo dlopen.lo fnmatch.lo getcwd.lo getline.lo getprogname.lo glob.lo isblank.lo memrchr.lo mksiglist.lo mktemp.lo nanosleep.lo setenv.lo siglist.lo snprintf.lo strlcat.lo strlcpy.lo strsignal.lo unsetenv.lo utimes.lo globtest.o fnm_test.o:; + $makefile =~ s:\@LTLIBOBJS\@:closefrom.lo dlopen.lo fnmatch.lo getcwd.lo getgrouplist.lo getline.lo getprogname.lo glob.lo isblank.lo memrchr.lo mksiglist.lo mktemp.lo nanosleep.lo setenv.lo siglist.lo snprintf.lo strlcat.lo strlcpy.lo strsignal.lo unsetenv.lo utimes.lo globtest.o fnm_test.o:; # Parse OBJS lines my %objs; diff --git a/plugins/sudoers/ldap.c b/plugins/sudoers/ldap.c index 10c6230e6..19cd5ece9 100644 --- a/plugins/sudoers/ldap.c +++ b/plugins/sudoers/ldap.c @@ -328,7 +328,7 @@ struct sudo_ldap_handle { LDAP *ld; struct ldap_result *result; char *username; - char **groups; + struct group_list *grlist; }; struct sudo_nss sudo_nss_ldap = { @@ -974,6 +974,7 @@ sudo_ldap_build_pass1(struct passwd *pw) { struct group *grp; char *buf, timebuffer[TIMEFILTER_LENGTH]; + struct group_list *grlist; size_t sz = 0; int i; @@ -984,16 +985,15 @@ sudo_ldap_build_pass1(struct passwd *pw) /* Then add (|(sudoUser=USERNAME)(sudoUser=ALL)) + NUL */ sz += 29 + strlen(pw->pw_name); - /* Add space for groups */ + /* Add space for primary and supplementary groups */ if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) { - sz += 12 + strlen(grp->gr_name); /* primary group */ - gr_delref(grp); + sz += 12 + strlen(grp->gr_name); } - if (strcmp(pw->pw_name, list_pw ? list_pw->pw_name : user_name) == 0) { - for (i = 0; i < user_ngroups; i++) { - if (user_gids[i] == pw->pw_gid) + if ((grlist = get_group_list(pw)) != NULL) { + for (i = 0; i < grlist->ngroups; i++) { + if (grp != NULL && strcasecmp(grlist->groups[i], grp->gr_name) == 0) continue; - sz += 12 + strlen(user_groups[i]); /* supplementary group */ + sz += 12 + strlen(grlist->groups[i]); } } @@ -1019,24 +1019,29 @@ sudo_ldap_build_pass1(struct passwd *pw) (void) strlcat(buf, ")", sz); /* Append primary group */ - if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) { + if (grp != NULL) { (void) strlcat(buf, "(sudoUser=%", sz); (void) strlcat(buf, grp->gr_name, sz); (void) strlcat(buf, ")", sz); - gr_delref(grp); } /* Append supplementary groups */ - if (strcmp(pw->pw_name, list_pw ? list_pw->pw_name : user_name) == 0) { - for (i = 0; i < user_ngroups; i++) { - if (user_gids[i] == pw->pw_gid) + if (grlist != NULL) { + for (i = 0; i < grlist->ngroups; i++) { + if (grp != NULL && strcasecmp(grlist->groups[i], grp->gr_name) == 0) continue; (void) strlcat(buf, "(sudoUser=%", sz); - (void) strlcat(buf, user_groups[i], sz); + (void) strlcat(buf, grlist->groups[i], sz); (void) strlcat(buf, ")", sz); } } + /* Done with groups. */ + if (grlist != NULL) + grlist_delref(grlist); + if (grp != NULL) + gr_delref(grp); + /* Add ALL to list and end the global OR */ if (strlcat(buf, "(sudoUser=ALL)", sz) >= sz) errorx(1, _("sudo_ldap_build_pass1 allocation mismatch")); @@ -2022,7 +2027,7 @@ sudo_ldap_open(struct sudo_nss *nss) handle->ld = ld; handle->result = NULL; handle->username = NULL; - handle->groups = NULL; + handle->grlist = NULL; nss->handle = handle; return 0; @@ -2280,7 +2285,7 @@ sudo_ldap_result_free_nss(struct sudo_nss *nss) efree(handle->username); handle->username = NULL; } - handle->groups = NULL; + handle->grlist = NULL; handle->result = NULL; } } @@ -2306,7 +2311,7 @@ sudo_ldap_result_get(struct sudo_nss *nss, struct passwd *pw) * have to contact the LDAP server again. */ if (handle->result) { - if (handle->groups == user_groups && + if (handle->grlist == user_group_list && strcmp(pw->pw_name, handle->username) == 0) { DPRINTF(("reusing previous result (user %s) with %d entries", handle->username, handle->result->nentries), 1); @@ -2380,7 +2385,7 @@ sudo_ldap_result_get(struct sudo_nss *nss, struct passwd *pw) /* Store everything in the sudo_nss handle. */ handle->result = lres; handle->username = estrdup(pw->pw_name); - handle->groups = user_groups; + handle->grlist = user_group_list; return lres; } diff --git a/plugins/sudoers/pwutil.c b/plugins/sudoers/pwutil.c index b0e4618e1..5d4cd0589 100644 --- a/plugins/sudoers/pwutil.c +++ b/plugins/sudoers/pwutil.c @@ -48,6 +48,12 @@ #ifdef HAVE_SETAUTHDB # include #endif /* HAVE_SETAUTHDB */ +#ifdef HAVE_UTMPX_H +# include +#else +# include +#endif /* HAVE_UTMPX_H */ +#include #include #include @@ -59,6 +65,7 @@ */ static struct rbtree *pwcache_byuid, *pwcache_byname; static struct rbtree *grcache_bygid, *grcache_byname; +static struct rbtree *grlist_cache; static int cmp_pwuid(const void *, const void *); static int cmp_pwnam(const void *, const void *); @@ -80,6 +87,7 @@ struct cache_item { union { struct passwd *pw; struct group *gr; + struct group_list *grlist; } d; }; @@ -160,8 +168,7 @@ make_pwitem(const struct passwd *pw, const char *name) total += strlen(name) + 1; /* Allocate space for struct item, struct passwd and the strings. */ - if ((item = malloc(total)) == NULL) - return NULL; + item = emalloc(total); cp = (char *) item + sizeof(struct cache_item); /* @@ -406,7 +413,7 @@ cmp_grgid(const void *v1, const void *v2) * elements. If name is non-NULL it is used as the key, else the * gid is the key. Fills in datum from struct group. */ -struct cache_item * +static struct cache_item * make_gritem(const struct group *gr, const char *name) { char *cp; @@ -428,8 +435,7 @@ make_gritem(const struct group *gr, const char *name) if (name != NULL) total += strlen(name) + 1; - if ((item = malloc(total)) == NULL) - return NULL; + item = emalloc(total); cp = (char *) item + sizeof(struct cache_item); /* @@ -467,6 +473,96 @@ make_gritem(const struct group *gr, const char *name) return item; } +#ifdef HAVE_UTMPX_H +# define GROUPNAME_LEN (sizeof((struct utmpx *)0)->ut_user) +#else +# ifdef HAVE_STRUCT_UTMP_UT_USER +# define GROUPNAME_LEN (sizeof((struct utmp *)0)->ut_user) +# else +# define GROUPNAME_LEN (sizeof((struct utmp *)0)->ut_name) +# endif +#endif /* HAVE_UTMPX_H */ + +/* + * Dynamically allocate space for a struct item plus the key and data + * elements. Fills in datum from the groups and gids arrays. + */ +static struct cache_item * +make_grlist_item(const char *user, GETGROUPS_T *gids, int ngids) +{ + char *cp; + size_t i, nsize, ngroups = 0, total, len; + struct cache_item *item; + struct group_list *grlist; + struct group *grp; + + /* Allocate in one big chunk for easy freeing. */ + nsize = strlen(user) + 1; + total = sizeof(struct cache_item) + sizeof(struct group_list) + nsize; + total += sizeof(char *) * ngids; + total += sizeof(gid_t *) * ngids; + total += GROUPNAME_LEN * ngids; + + item = emalloc(total); + cp = (char *) item + sizeof(struct cache_item); + + /* + * Copy in group list and make pointers relative to space + * at the end of the buffer. Note that the gids array must come + * immediately after struct group to guarantee proper alignment. + */ + grlist = (struct group_list *)cp; + zero_bytes(grlist, sizeof(struct group_list)); + cp += sizeof(struct group_list); + grlist->gids = (gid_t *)cp; + cp += sizeof(gid_t) * ngids; + grlist->groups = (char **)cp; + cp += sizeof(char *) * ngids; + + /* Set key and datum. */ + memcpy(cp, user, nsize); + item->k.name = cp; + item->d.grlist = grlist; + item->refcnt = 1; + cp += nsize; + + /* + * Store group IDs. + */ + for (i = 0; i < ngids; i++) + grlist->gids[i] = gids[i]; + grlist->ngids = ngids; + +#ifdef HAVE_SETAUTHDB + aix_setauthdb((char *) user); +#endif + /* + * Resolve group names by ID and store at the end. + */ + for (i = 0; i < ngids; i++) { + if ((grp = sudo_getgrgid(gids[i])) != NULL) { + len = strlen(grp->gr_name) + 1; + if (cp - (char *)grlist + len > total) { + void *ptr = erealloc(grlist, total + len + GROUPNAME_LEN); + total += len + GROUPNAME_LEN; + cp = (char *)ptr + (cp - (char *)grlist); + grlist = ptr; + } + memcpy(cp, grp->gr_name, len); + grlist->groups[ngroups++] = cp; + cp += len; + gr_delref(grp); + } + } + grlist->ngroups = ngroups; + +#ifdef HAVE_SETAUTHDB + aix_restoreauthdb(); +#endif + + return item; +} + void gr_addref(struct group *gr) { @@ -606,6 +702,27 @@ sudo_fakegrnam(const char *group) return gr; } +void +grlist_addref(struct group_list *grlist) +{ + ptr_to_item(grlist)->refcnt++; +} + +static void +grlist_delref_item(void *v) +{ + struct cache_item *item = v; + + if (--item->refcnt == 0) + efree(item); +} + +void +grlist_delref(struct group_list *grlist) +{ + grlist_delref_item(ptr_to_item(grlist)); +} + void sudo_setgrent(void) { @@ -614,6 +731,8 @@ sudo_setgrent(void) grcache_bygid = rbcreate(cmp_grgid); if (grcache_byname == NULL) grcache_byname = rbcreate(cmp_grnam); + if (grlist_cache == NULL) + grlist_cache = rbcreate(cmp_grnam); } void @@ -627,6 +746,10 @@ sudo_freegrcache(void) rbdestroy(grcache_byname, gr_delref_item); grcache_byname = NULL; } + if (grlist_cache != NULL) { + rbdestroy(grlist_cache, grlist_delref_item); + grlist_cache = NULL; + } } void @@ -636,46 +759,130 @@ sudo_endgrent(void) sudo_freegrcache(); } -#if defined(HAVE_GETGROUPS) && !defined(HAVE_MBR_CHECK_MEMBERSHIP) -static int -user_in_group_cached(const char *group) +struct group_list * +get_group_list(struct passwd *pw) { - gid_t gid = -1; - int i, retval = FALSE; + struct cache_item key, *item; + struct rbnode *node; + size_t len; + GETGROUPS_T *gids; + int ngids; - if (group[0] == '#') - gid = atoi(group + 1); - - /* Check against user's primary (passwd file) group. */ - if ((user_group != NULL && strcasecmp(group, user_group) == 0) || - (group[0] == '#' && gid == user_gid)) { - retval = TRUE; + key.k.name = pw->pw_name; + if ((node = rbfind(grlist_cache, &key)) != NULL) { + item = (struct cache_item *) node->data; goto done; } + /* + * Cache group db entry if it exists or a negative response if not. + */ +#if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX) + ngids = sysconf(_SC_NGROUPS_MAX) * 2; + if (ngids < 0) +#endif + ngids = NGROUPS_MAX * 2; + gids = emalloc2(ngids, sizeof(GETGROUPS_T)); + if (getgrouplist(pw->pw_name, pw->pw_gid, gids, &ngids) == -1) { + efree(gids); + gids = emalloc2(ngids, sizeof(GETGROUPS_T)); + if (getgrouplist(pw->pw_name, pw->pw_gid, gids, &ngids) == -1) { + efree(gids); + return NULL; + } + } + if (ngids > 0) { + if ((item = make_grlist_item(pw->pw_name, gids, ngids)) == NULL) + errorx(1, "unable to parse group list for %s", pw->pw_name); + efree(gids); + if (rbinsert(grlist_cache, item) != NULL) + errorx(1, "unable to cache group list for %s, already exists", + pw->pw_name); + } else { + /* Should not happen. */ + len = strlen(pw->pw_name) + 1; + item = emalloc(sizeof(*item) + len); + item->refcnt = 1; + item->k.name = (char *) item + sizeof(*item); + memcpy(item->k.name, pw->pw_name, len); + item->d.grlist = NULL; + if (rbinsert(grlist_cache, item) != NULL) + errorx(1, "unable to cache group list for %s, already exists", + pw->pw_name); + } +done: + item->refcnt++; + return item->d.grlist; +} + +void +set_group_list(const char *user, GETGROUPS_T *gids, int ngids) +{ + struct cache_item key, *item; + struct rbnode *node; /* - * If we are matching the invoking or list user and that user has a - * supplementary group vector, check it. + * Cache group db entry if it doesn't already exist */ - for (i = 0; i < user_ngroups; i++) { - if (strcasecmp(group, user_groups[i]) == 0) { + key.k.name = (char *) user; + if ((node = rbfind(grlist_cache, &key)) == NULL) { + if ((item = make_grlist_item(user, gids, ngids)) == NULL) + errorx(1, "unable to parse group list for %s", user); + if (rbinsert(grlist_cache, item) != NULL) + errorx(1, "unable to cache group list for %s, already exists", + user); + } +} + +#ifndef HAVE_MBR_CHECK_MEMBERSHIP +static int +user_in_group_list(struct passwd *pw, struct group_list *grlist, const char *group) +{ + struct group *grp = NULL; + int i, retval = FALSE; + + /* + * If it could be a sudo-style group ID check gids first. + */ + if (group[0] == '#') { + gid_t gid = atoi(group + 1); + if (gid == pw->pw_gid) { retval = TRUE; goto done; } - } - if (group[0] == '#') { - for (i = 0; i < user_ngroups; i++) { - if (gid == user_gids[i]) { + for (i = 0; i < grlist->ngids; i++) { + if (gid == grlist->gids[i]) { retval = TRUE; goto done; } } } + + /* + * Next check the supplementary group vector. + * It usually includes the password db group too. + */ + for (i = 0; i < grlist->ngroups; i++) { + if (strcasecmp(group, grlist->groups[i]) == 0) { + retval = TRUE; + goto done; + } + } + + /* Finally check against user's primary (passwd file) group. */ + if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) { + if (strcasecmp(group, grp->gr_name) == 0) { + retval = TRUE; + goto done; + } + } + done: + if (grp != NULL) + gr_delref(grp); return retval; } -#endif /* HAVE_GETGROUPS && !HAVE_MBR_CHECK_MEMBERSHIP */ +#endif /* !HAVE_MBR_CHECK_MEMBERSHIP */ int user_in_group_lookup(struct passwd *pw, const char *group) @@ -743,11 +950,18 @@ done: int user_in_group(struct passwd *pw, const char *group) { -#if defined(HAVE_GETGROUPS) && !defined(HAVE_MBR_CHECK_MEMBERSHIP) - if (user_ngroups > 0 && - strcmp(pw->pw_name, list_pw ? list_pw->pw_name : user_name) == 0) { - return user_in_group_cached(group); +#ifndef HAVE_MBR_CHECK_MEMBERSHIP + struct group_list *grlist; + + if ((grlist = get_group_list(pw)) != NULL) { + /* The base gid (from passwd) is always present. */ + if (grlist->ngids > 1) { + int matched = user_in_group_list(pw, grlist, group); + grlist_delref(grlist); + return matched; + } + grlist_delref(grlist); } -#endif /* HAVE_GETGROUPS && !HAVE_MBR_CHECK_MEMBERSHIP */ +#endif /* !HAVE_MBR_CHECK_MEMBERSHIP */ return user_in_group_lookup(pw, group); } diff --git a/plugins/sudoers/set_perms.c b/plugins/sudoers/set_perms.c index 03c95e517..4ad5ef7a3 100644 --- a/plugins/sudoers/set_perms.c +++ b/plugins/sudoers/set_perms.c @@ -50,7 +50,7 @@ /* * Prototypes */ -static void runas_setgroups(void); +static struct group_list *runas_setgroups(void); /* * We keep track of the current permisstions and use a stack to restore @@ -67,20 +67,13 @@ struct perm_state { #ifdef HAVE_SETRESUID gid_t sgid; #endif - GETGROUPS_T *gids; - int ngroups; + struct group_list *grlist; }; #define PERM_STACK_MAX 16 static struct perm_state perm_stack[PERM_STACK_MAX]; static int perm_stack_depth = 0; -/* XXX - make a runas_user struct? */ -int runas_ngroups = -1; -#ifdef HAVE_GETGROUPS -GETGROUPS_T *runas_gids; -#endif - #undef ID #define ID(x) (state->x == ostate->x ? -1 : state->x) #undef OID @@ -91,6 +84,7 @@ rewind_perms(void) { while (perm_stack_depth > 1) restore_perms(); + grlist_delref(perm_stack[0].grlist); } #ifdef HAVE_SETRESUID @@ -146,8 +140,8 @@ set_perms(int perm) state->egid = getegid(); state->sgid = state->egid; /* in case we are setgid */ #endif - state->gids = user_gids; - state->ngroups = user_ngroups; + state->grlist = user_group_list; + grlist_addref(user_group_list); break; case PERM_ROOT: @@ -161,15 +155,15 @@ set_perms(int perm) state->rgid = -1; state->egid = -1; state->sgid = -1; - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); break; case PERM_USER: - state->gids = user_gids; - state->ngroups = user_ngroups; - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(state->ngroups, state->gids)) { + state->grlist = user_group_list; + grlist_addref(user_group_list); + if (state->grlist != ostate->grlist) { + if (setgroups(state->grlist->ngids, state->grlist->gids)) { errstr = "setgroups()"; goto bad; } @@ -192,10 +186,10 @@ set_perms(int perm) case PERM_FULL_USER: /* headed for exec() */ - state->gids = user_gids; - state->ngroups = user_ngroups; - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(state->ngroups, state->gids)) { + state->grlist = user_group_list; + grlist_addref(user_group_list); + if (state->grlist != ostate->grlist) { + if (setgroups(state->grlist->ngids, state->grlist->gids)) { errstr = "setgroups()"; goto bad; } @@ -217,10 +211,7 @@ set_perms(int perm) break; case PERM_RUNAS: - runas_setgroups(); - state->gids = runas_gids; - state->ngroups = runas_ngroups; - + state->grlist = runas_setgroups(); state->rgid = -1; state->egid = runas_gr ? runas_gr->gr_gid : runas_pw->pw_gid; state->sgid = -1; @@ -238,8 +229,8 @@ set_perms(int perm) break; case PERM_SUDOERS: - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); /* assumes euid == ROOT_UID, ruid == user */ state->rgid = -1; @@ -266,8 +257,8 @@ set_perms(int perm) break; case PERM_TIMESTAMP: - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); state->rgid = -1; state->egid = -1; state->sgid = -1; @@ -323,12 +314,13 @@ restore_perms(void) state->egid, state->sgid, OID(rgid), OID(egid), OID(sgid)); goto bad; } - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(ostate->ngroups, ostate->gids)) { + if (state->grlist != ostate->grlist) { + if (setgroups(ostate->grlist->ngids, ostate->grlist->gids)) { warning("setgroups()"); goto bad; } } + grlist_delref(state->grlist); return; bad: @@ -375,7 +367,7 @@ set_perms(int perm) state->rgid = getgid(); state->egid = getegid(); state->gids = user_gids; - state->ngroups = user_ngroups; + state->ngids = user_ngids; break; case PERM_ROOT: @@ -395,15 +387,14 @@ set_perms(int perm) state->euid = ROOT_UID; state->rgid = -1; state->egid = -1; - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); break; case PERM_USER: - state->gids = user_gids; - state->ngroups = user_ngroups; - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(state->ngroups, state->gids)) { + state->grlist = user_group_list; + if (state->grlist != ostate->grlist) { + if (setgroups(state->grlist->ngids, state->grlist->gids)) { errstr = "setgroups()"; goto bad; } @@ -424,10 +415,9 @@ set_perms(int perm) case PERM_FULL_USER: /* headed for exec() */ - state->gids = user_gids; - state->ngroups = user_ngroups; - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(state->ngroups, state->gids)) { + state->grlist = user_group_list; + if (state->grlist != ostate->grlist) { + if (setgroups(state->grlist->ngids, state->grlist->gids)) { errstr = "setgroups()"; goto bad; } @@ -447,10 +437,7 @@ set_perms(int perm) break; case PERM_RUNAS: - runas_setgroups(); - state->gids = runas_gids; - state->ngroups = runas_ngroups; - + state->grlist = runas_setgroups(); state->rgid = -1; state->egid = runas_gr ? runas_gr->gr_gid : runas_pw->pw_gid; if (setregid(ID(rgid), ID(egid))) { @@ -466,8 +453,8 @@ set_perms(int perm) break; case PERM_SUDOERS: - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); /* assume euid == ROOT_UID, ruid == user */ state->rgid = -1; @@ -492,8 +479,8 @@ set_perms(int perm) break; case PERM_TIMESTAMP: - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); state->rgid = -1; state->egid = -1; state->ruid = ROOT_UID; @@ -552,12 +539,13 @@ restore_perms(void) state->egid, OID(rgid), OID(egid)); goto bad; } - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(ostate->ngroups, ostate->gids)) { + if (state->grlist != ostate->grlist) { + if (setgroups(ostate->grlist->ngids, ostate->grlist->gids)) { warning("setgroups()"); goto bad; } } + grlist_delref(state->grlist); return; bad: @@ -620,7 +608,7 @@ set_perms(int perm) state->rgid = getgid(); state->egid = getegid(); state->gids = user_gids; - state->ngroups = user_ngroups; + state->ngids = user_ngids; break; case PERM_ROOT: @@ -629,15 +617,14 @@ set_perms(int perm) state->euid = ROOT_UID; state->rgid = -1; state->egid = -1; - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); break; case PERM_USER: - state->gids = user_gids; - state->ngroups = user_ngroups; - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(state->ngroups, state->gids)) { + state->grlist = user_group_list; + if (state->grlist != ostate->grlist) { + if (setgroups(state->grlist->ngids, state->grlist->gids)) { errstr = "setgroups()"; goto bad; } @@ -658,10 +645,9 @@ set_perms(int perm) case PERM_FULL_USER: /* headed for exec() */ - state->gids = user_gids; - state->ngroups = user_ngroups; - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(state->ngroups, state->gids)) { + state->grlist = user_group_list; + if (state->grlist != ostate->grlist) { + if (setgroups(state->grlist->ngids, state->grlist->gids)) { errstr = "setgroups()"; goto bad; } @@ -681,10 +667,7 @@ set_perms(int perm) break; case PERM_RUNAS: - runas_setgroups(); - state->gids = runas_gids; - state->ngroups = runas_ngroups; - + state->grlist = runas_setgroups(); state->rgid = -1; state->egid = runas_gr ? runas_gr->gr_gid : runas_pw->pw_gid; if (setegid(ID(egid))) { @@ -700,8 +683,8 @@ set_perms(int perm) break; case PERM_SUDOERS: - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); /* assume euid == ROOT_UID, ruid == user */ state->rgid = -1; @@ -726,8 +709,8 @@ set_perms(int perm) break; case PERM_TIMESTAMP: - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); state->rgid = -1; state->egid = -1; state->ruid = ROOT_UID; @@ -781,8 +764,8 @@ restore_perms(void) warning("setegid(%d)", OID(egid)); goto bad; } - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(ostate->ngroups, ostate->gids)) { + if (state->grlist != ostate->grlist) { + if (setgroups(ostate->grlist->ngids, ostate->grlist->gids)) { warning("setgroups()"); goto bad; } @@ -791,6 +774,7 @@ restore_perms(void) warning("seteuid(%d)", OID(euid)); goto bad; } + grlist_delref(state->grlist); return; bad: @@ -833,14 +817,14 @@ set_perms(int perm) state->ruid = getuid(); state->rgid = getgid(); state->gids = user_gids; - state->ngroups = user_ngroups; + state->ngids = user_ngids; break; case PERM_ROOT: state->ruid = ROOT_UID; state->rgid = -1; - state->gids = NULL; - state->ngroups = -1; + state->grlist = ostate->grlist; + grlist_addref(state->grlist); if (setuid(ROOT_UID)) { errstr = "setuid(ROOT_UID)"; goto bad; @@ -848,10 +832,9 @@ set_perms(int perm) break; case PERM_FULL_USER: - state->gids = user_gids; - state->ngroups = user_ngroups; - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(state->ngroups, state->gids)) { + state->grlist = user_group_list; + if (state->grlist != ostate->grlist) { + if (setgroups(state->grlist->ngids, state->grlist->gids)) { errstr = "setgroups()"; goto bad; } @@ -897,12 +880,13 @@ restore_perms(void) ostate = &perm_stack[perm_stack_depth - 2]; perm_stack_depth--; - if (state->ngroups != -1 && state->gids != ostate->gids) { - if (setgroups(ostate->ngroups, ostate->gids)) { + if (state->grlist != ostate->grlist) { + if (setgroups(ostate->grlist->ngids, ostate->grlist->gids)) { warning("setgroups()"); goto bad; } } + grlist_delref(state->grlist); if (OID(rgid) != -1 && setgid(ostate->rgid)) { warning("setgid(%d)", ostate->rgid); goto bad; @@ -920,53 +904,26 @@ bad: # endif /* HAVE_SETREUID */ #endif /* HAVE_SETRESUID */ -#ifdef HAVE_INITGROUPS -static void -runas_setgroups() +static struct group_list * +runas_setgroups(void) { - static struct passwd *pw; - struct passwd *opw = pw; + struct passwd *pw; + struct group_list *grlist; - if (def_preserve_groups) - return; - - /* - * Use stashed copy of runas groups if available, else initgroups and stash. - */ - pw = runas_pw ? runas_pw : sudo_user.pw; - if (pw != opw) { - pw = runas_pw ? runas_pw : sudo_user.pw; -# ifdef HAVE_SETAUTHDB - aix_setauthdb(pw->pw_name); -# endif - if (initgroups(pw->pw_name, pw->pw_gid) < 0) - log_error(USE_ERRNO|MSG_ONLY, _("unable to set runas group vector")); -# ifdef HAVE_GETGROUPS - if (runas_gids) { - efree(runas_gids); - runas_gids = NULL; - } - if ((runas_ngroups = getgroups(0, NULL)) > 0) { - runas_gids = emalloc2(runas_ngroups, sizeof(GETGROUPS_T)); - if (getgroups(runas_ngroups, runas_gids) < 0) - log_error(USE_ERRNO|MSG_ONLY, _("unable to get runas group vector")); - } -# ifdef HAVE_SETAUTHDB - aix_restoreauthdb(); -# endif - } else { - if (setgroups(runas_ngroups, runas_gids) < 0) - log_error(USE_ERRNO|MSG_ONLY, _("unable to set runas group vector")); -# endif /* HAVE_GETGROUPS */ + if (def_preserve_groups) { + grlist_addref(user_group_list); + return user_group_list; } + + pw = runas_pw ? runas_pw : sudo_user.pw; +#ifdef HAVE_SETAUTHDB + aix_setauthdb(pw->pw_name); +#endif + grlist = get_group_list(pw); +#ifdef HAVE_SETAUTHDB + aix_restoreauthdb(); +#endif + if (setgroups(grlist->ngids, grlist->gids) < 0) + log_error(USE_ERRNO|MSG_ONLY, _("unable to set runas group vector")); + return grlist; } - -#else - -static void -runas_setgroups() -{ - /* STUB */ -} - -#endif /* HAVE_INITGROUPS */ diff --git a/plugins/sudoers/sudo_nss.c b/plugins/sudoers/sudo_nss.c index 5c3835943..de0d5a6bb 100644 --- a/plugins/sudoers/sudo_nss.c +++ b/plugins/sudoers/sudo_nss.c @@ -204,47 +204,6 @@ sudo_read_nss(void) #endif /* HAVE_LDAP && _PATH_NSSWITCH_CONF */ -/* Reset user_gids and user_groups based on passwd entry. */ -static void -reset_groups(struct passwd *pw) -{ -#if defined(HAVE_INITGROUPS) && defined(HAVE_GETGROUPS) - if (pw != sudo_user.pw) { - struct group *grp; - int i; - -# ifdef HAVE_SETAUTHDB - aix_setauthdb(pw->pw_name); -# endif - if (initgroups(pw->pw_name, pw->pw_gid) == -1) - log_error(USE_ERRNO|MSG_ONLY, _("unable to reset group vector")); - efree(user_gids); - user_gids = NULL; - efree(user_groups); - user_groups = NULL; - if ((user_ngroups = getgroups(0, NULL)) > 0) { - user_gids = emalloc2(user_ngroups, sizeof(GETGROUPS_T)); - if (getgroups(user_ngroups, user_gids) < 0) - log_error(USE_ERRNO|MSG_ONLY, _("unable to get group vector")); - user_groups = emalloc2(user_ngroups, sizeof(char *)); - for (i = 0; i < user_ngroups; i++) { - grp = sudo_getgrgid(user_gids[i]); - if (grp != NULL) { - user_groups[i] = estrdup(grp->gr_name); - gr_delref(grp); - } else { - easprintf(&user_groups[i], "#%u", - (unsigned int) user_gids[i]); - } - } - } -# ifdef HAVE_SETAUTHDB - aix_restoreauthdb(); -# endif - } -#endif -} - static int output(const char *buf) { @@ -272,9 +231,6 @@ display_privs(struct sudo_nss_list *snl, struct passwd *pw) struct lbuf defs, privs; int count, olen; - /* Reset group vector so group matching works correctly. */ - reset_groups(pw); - lbuf_init(&defs, output, 4, NULL, sudo_user.cols); lbuf_init(&privs, output, 4, NULL, sudo_user.cols); @@ -333,9 +289,6 @@ display_cmnd(struct sudo_nss_list *snl, struct passwd *pw) { struct sudo_nss *nss; - /* Reset group vector so group matching works correctly. */ - reset_groups(pw); - tq_foreach_fwd(snl, nss) { if (nss->display_cmnd(nss, pw) == 0) return TRUE; diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index b8beeae8a..388ddb55c 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -99,10 +99,6 @@ static int deserialize_info(char * const settings[], char * const user_info[]); static char *find_editor(int nfiles, char **files, char ***argv_out); static void create_admin_success_flag(void); -/* XXX */ -extern int runas_ngroups; -extern GETGROUPS_T *runas_gids; - /* * Globals */ @@ -272,6 +268,8 @@ sudoers_policy_close(int exit_status, int error_code) pw_delref(runas_pw); if (runas_gr != NULL) gr_delref(runas_gr); + if (user_group_list != NULL) + grlist_delref(user_group_list); } /* @@ -642,22 +640,24 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], } if (def_preserve_groups) { command_info[info_len++] = "preserve_groups=true"; - } else if (runas_ngroups != -1) { + } else { int i, len; size_t glsize; char *cp, *gid_list; + struct group_list *grlist = get_group_list(runas_pw); - glsize = sizeof("runas_groups=") - 1 + (runas_ngroups * (MAX_UID_T_LEN + 1)); + glsize = sizeof("runas_groups=") - 1 + (grlist->ngids * (MAX_UID_T_LEN + 1)); gid_list = emalloc(glsize); memcpy(gid_list, "runas_groups=", sizeof("runas_groups=") - 1); cp = gid_list + sizeof("runas_groups=") - 1; - for (i = 0; i < runas_ngroups; i++) { + for (i = 0; i < grlist->ngids; i++) { /* XXX - check rval */ len = snprintf(cp, glsize - (cp - gid_list), "%s%u", - i ? "," : "", (unsigned int) runas_gids[i]); + i ? "," : "", (unsigned int) grlist->gids[i]); cp += len; } command_info[info_len++] = gid_list; + grlist_delref(grlist); } if (def_closefrom >= 0) easprintf(&command_info[info_len++], "closefrom=%d", def_closefrom); @@ -821,6 +821,13 @@ init_vars(char * const envp[]) log_error(0, _("unknown user: %s"), user_name); /* NOTREACHED */ } + + /* + * Get group list. + */ + if (user_group_list == NULL) + user_group_list = get_group_list(sudo_user.pw); + #ifdef HAVE_MBR_CHECK_MEMBERSHIP mbr_uid_to_uuid(user_uid, user_uuid); #endif @@ -1151,7 +1158,7 @@ deserialize_info(char * const settings[], char * const user_info[]) { struct group *grp; char * const *cur; - const char *p; + const char *p, *groups = NULL; int flags = 0; #define MATCHES(s, v) (strncmp(s, v, sizeof(v) - 1) == 0) @@ -1297,37 +1304,7 @@ deserialize_info(char * const settings[], char * const user_info[]) continue; } if (MATCHES(*cur, "groups=")) { - /* Count number of groups */ - const char *val = *cur + sizeof("groups=") - 1; - const char *cp; - if (val[0] != '\0') { - user_ngroups = 1; - for (cp = val; *cp != '\0'; cp++) { - if (*cp == ',') - user_ngroups++; - } - - user_gids = emalloc2(user_ngroups, sizeof(GETGROUPS_T)); - user_groups = emalloc2(user_ngroups, sizeof(char *)); - user_ngroups = 0; - cp = val; - for (;;) { - /* XXX - strtol would be better here */ - grp = sudo_getgrgid(atoi(cp)); - if (grp != NULL) { - user_gids[user_ngroups] = grp->gr_gid; - user_groups[user_ngroups] = estrdup(grp->gr_name); - gr_delref(grp); - } else { - easprintf(&user_groups[user_ngroups], "#%s", cp); - } - user_ngroups++; - cp = strchr(cp, ','); - if (cp == NULL) - break; - cp++; /* skip over comma */ - } - } + groups = *cur + sizeof("groups=") - 1; continue; } if (MATCHES(*cur, "cwd=")) { @@ -1360,6 +1337,36 @@ deserialize_info(char * const settings[], char * const user_info[]) if (user_tty == NULL) user_tty = "unknown"; /* user_ttypath remains NULL */ + if (groups != NULL && groups[0] != '\0') { + const char *cp; + GETGROUPS_T *gids; + int ngids; + + /* Count number of groups, including passwd gid. */ + ngids = 2; + for (cp = groups; *cp != '\0'; cp++) { + if (*cp == ',') + ngids++; + } + + /* The first gid in the list is the passwd group gid. */ + gids = emalloc2(ngids, sizeof(GETGROUPS_T)); + gids[0] = user_gid; + ngids = 1; + cp = groups; + for (;;) { + gids[ngids] = atoi(cp); + if (gids[0] != gids[ngids]) + ngids++; + cp = strchr(cp, ','); + if (cp == NULL) + break; + cp++; /* skip over comma */ + } + set_group_list(user_name, gids, ngids); + efree(gids); + } + #undef MATCHES return flags; } diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index d8f33a22d..6ec375bf8 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -42,6 +42,16 @@ # include #endif +/* + * Password db and supplementary group IDs with associated group names. + */ +struct group_list { + char **groups; + GETGROUPS_T *gids; + int ngroups; + int ngids; +}; + /* * Info pertaining to the invoking user. */ @@ -64,8 +74,7 @@ struct sudo_user { char *class_name; char *krb5_ccname; char *group; - char **groups; - GETGROUPS_T *gids; + struct group_list *group_list; char * const * env_vars; #ifdef HAVE_SELINUX char *role; @@ -74,11 +83,10 @@ struct sudo_user { char *cwd; char *iolog_file; int closefrom; - int ngroups; - uid_t uid; - uid_t gid; int lines; int cols; + uid_t uid; + uid_t gid; #ifdef HAVE_MBR_CHECK_MEMBERSHIP uuid_t uuid; #endif @@ -160,10 +168,8 @@ struct sudo_user { #define user_passwd (sudo_user.pw->pw_passwd) #define user_uuid (sudo_user.uuid) #define user_dir (sudo_user.pw->pw_dir) -#define user_ngroups (sudo_user.ngroups) -#define user_gids (sudo_user.gids) #define user_group (sudo_user.group) -#define user_groups (sudo_user.groups) +#define user_group_list (sudo_user.group_list) #define user_tty (sudo_user.tty) #define user_ttypath (sudo_user.ttypath) #define user_cwd (sudo_user.cwd) @@ -268,12 +274,16 @@ void sudo_setpwent(void); void sudo_endpwent(void); void sudo_setspent(void); void sudo_endspent(void); +struct group_list *get_group_list(struct passwd *pw); +void set_group_list(const char *, GETGROUPS_T *gids, int ngids); struct passwd *sudo_getpwnam(const char *); struct passwd *sudo_fakepwnam(const char *, gid_t); struct passwd *sudo_getpwuid(uid_t); struct group *sudo_getgrnam(const char *); struct group *sudo_fakegrnam(const char *); struct group *sudo_getgrgid(gid_t); +void grlist_addref(struct group_list *); +void grlist_delref(struct group_list *); void gr_addref(struct group *); void gr_delref(struct group *); void pw_addref(struct passwd *); diff --git a/src/sudo.c b/src/sudo.c index 8814df3e4..1fc471b53 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -343,7 +343,6 @@ static char * get_user_groups(struct user_details *ud) { char *gid_list = NULL; -#ifdef HAVE_GETGROUPS size_t glsize; char *cp; int i, len; @@ -364,7 +363,6 @@ get_user_groups(struct user_details *ud) i ? "," : "", (unsigned int)ud->groups[i]); cp += len; } -#endif return gid_list; } @@ -929,19 +927,12 @@ exec_setup(struct command_details *details, const char *ptyname, int ptyfd) } if (!ISSET(details->flags, CD_PRESERVE_GROUPS)) { -#ifdef HAVE_GETGROUPS if (details->ngroups >= 0) { if (setgroups(details->ngroups, details->groups) < 0) { warning(_("unable to set supplementary group IDs")); goto done; } } -#else - if (pw && initgroups(pw->pw_name, pw->pw_gid) < 0) { - warning(_("unable to set supplementary group IDs")); - goto done; - } -#endif } if (ISSET(details->flags, CD_SET_PRIORITY)) {