public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] nsswitch: Add group merging support
@ 2015-12-16 15:11 Stephen Gallagher
  2015-12-29 21:58 ` Mike Frysinger
  2016-03-28 13:14 ` Florian Weimer
  0 siblings, 2 replies; 33+ messages in thread
From: Stephen Gallagher @ 2015-12-16 15:11 UTC (permalink / raw)
  To: libc-alpha

https://sourceware.org/glibc/wiki/Proposals/GroupMerging

== Justification ==
It is common today for users to rely on centrally-managed user stores for
handling their user accounts. However, much software existing today does
not have an innate understanding of such accounts. Instead, they commonly
rely on membership in known groups for managing access-control (for
example the "wheel" group on Fedora and RHEL systems or the "adm" group
on Debian-derived systems). In the present incarnation of nsswitch, the
only way to have such groups managed by a remote user store such as
FreeIPA or Active Directory would be to manually remove the groups from
/etc/group on the clients so that nsswitch would then move past nss_files
and into the SSSD, nss-ldap or other remote user database.

== Solution ==
With this patch, a new action is introduced for nsswitch:
NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
between two database entries in the nsswitch.conf file. When a group is
located in the first of the two group entries, processing will continue
on to the next one. If the group is also found in the next entry (and the
group name and GID are an exact match), the member list of the second
entry will be added to the group object to be returned.

== Implementation ==
After each DL_LOOKUP_FN() returns, the next action is checked. If the
function returned NSS_STATUS_SUCCESS and the next action is
NSS_ACTION_MERGE, a copy of the result buffer is saved for the next pass
through the loop. If on this next pass through the loop the database
returns another instance of a group matching both the group name and GID,
the member list is added to the previous list and it is returned as a
single object. If the following database does not contain the same group,
then the original is copied back into the destination buffer.

This patch implements merge functionality only for the group database.
For other databases, there is a default implementation that will return
the EINVAL errno if a merge is requested. The merge functionality can be
implemented for other databases at a later time if such is needed. Each
database must provide a unique implementation of the deep-copy and merge
functions.

If [SUCCESS=merge] is present in nsswitch.conf for a glibc version that
does not support it, glibc will process results up until that operation,
at which time it will return results if it has found them or else will
simply return an error. In practical terms, this ends up behaving like
the remainder of the nsswitch.conf line does not exist.

== Iterators ==
This feature does not modify the iterator functionality from its current
behavior. If getgrnam() or getgrgid() is called, glibc will iterate
through all entries in the `group` line in nsswitch.conf and display the
list of members without attempting to merge them. This is consistent with
the behavior of nss_files where if two separate lines are specified for
the same group in /etc/groups, getgrnam()/getgrgid() will display both.
Clients are already expected to handle this gracefully.

== No Premature Optimizations ==
The following is a list of places that might be eligible for
optimization, but were not overengineered for this initial contribution:
 * Any situation where a merge may occur will result in one malloc() of
   the same size as the input buffer.
 * Any situation where a merge does occur will result in a second
   malloc() to hold the list of pointers to member name strings.
 * The list of members is simply concatenated together and is not tested
   for uniqueness (which is identical to the behavior for nss_files,
   which will simply return identical values if they both exist on the
   line in the file. This could potentially be optimized to reduce space
   usage in the buffer, but it is both complex and computationally
   expensive to do so.

== Testing ==
I performed testing by running the getent utility against my newly-built
glibc and configuring /etc/nsswitch.conf with the following entry:
group: group:      files [SUCCESS=merge] sss

In /etc/group I included the line:
wheel:x:10:sgallagh

I then configured my local SSSD using the id_provider=local to respond
with:
wheel:*:10:localuser,localuser2

I then ran `getent group wheel` against the newly-built glibc in
multiple situations and received the expected output as described
above:
 * When SSSD was running.
 * When SSSD was configured in nsswitch.conf but the daemon was not
   running.
 * When SSSD was configured in nsswitch.conf but nss_sss.so.2 was not
   installed on the system.
 * When the order of 'sss' and 'files' was reversed.
 * All of the above with the [SUCCESS=merge] removed (to ensure no
   regressions).
 * All of the above with `getent group 10`.
 * All of the above with `getent group` with and without
   `enumerate=true` set in SSSD.
 * All of the above with and without nscd enabled on the system.

== NEWS ==

* A new NSS action is added to facilitate large distribution system
  administration.  The action, MERGE, allows remote user stores like
  LDAP to be merged into local user stores like /etc/groups in order
  to provide easy to use, updated, and managed sets of merged
  credentials.  The new action can be used by configuring it in
  /etc/nsswitch.conf:
  group: files [SUCCESS=merge] nis
  Implemented by Stephen Gallagher (Red Hat).

== ChangeLog ==

2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>

	[BZ #19072]
	* grp/Makefile (headers): Add grp-merge.h
	(routines): Add grp-merge.
	* grp/getgrgid_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/getgrname_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/grp-merge.c: New file.
	* grp/grp-merge.h: New file.
	* manual/nss.texi (Actions in the NSS configuration): Describe
	return, continue, and merge.
	* nscd/Makefile: Add vpath to find grp-merge.c
	(nscd-modules): Add grp-merge.
	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
	[!MERGE_FN]: Define __merge_einval.
	(CHECK_MERGE): Define.
	(REENTRANT_NAME): Process merge if do_merge is true.
	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
	(__nss_getent_r): Likewise.
	* nss/nsswitch.c (nss_parse_service_list): Likewise.
	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

Resolves BZ #19072
---
 grp/Makefile      |   5 +-
 grp/getgrgid_r.c  |   4 ++
 grp/getgrnam_r.c  |   4 ++
 grp/grp-merge.c   | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 grp/grp-merge.h   |  40 ++++++++++++
 manual/nss.texi   |  42 ++++++++++++-
 nscd/Makefile     |   5 +-
 nscd/getgrgid_r.c |   4 ++
 nscd/getgrnam_r.c |   4 ++
 nss/getXXbyYY_r.c | 109 +++++++++++++++++++++++++++++++-
 nss/getnssent_r.c |  36 ++++++++++-
 nss/nsswitch.c    |   3 +
 nss/nsswitch.h    |   3 +-
 13 files changed, 434 insertions(+), 10 deletions(-)
 create mode 100644 grp/grp-merge.c
 create mode 100644 grp/grp-merge.h

diff --git a/grp/Makefile b/grp/Makefile
index ed8cc2b0564f0e3842cd78f24a4e0788d659bbc4..52af992365268aae8cf8a80cd7216160b1431e84 100644
--- a/grp/Makefile
+++ b/grp/Makefile
@@ -20,15 +20,16 @@
 #
 subdir	:= grp
 
 include ../Makeconfig
 
-headers := grp.h
+headers := grp.h grp-merge.h
 
 routines := fgetgrent initgroups setgroups \
 	    getgrent getgrgid getgrnam putgrent \
-	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r
+	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r \
+	    grp-merge
 
 tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
index 05d4d772d3ef0bfae8f9375387c41310885ce41a..5bd32b9881e54a46262df668542d5f925c44dd93 100644
--- a/grp/getgrgid_r.c
+++ b/grp/getgrgid_r.c
@@ -16,14 +16,18 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
+
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
 
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c
index 0061cb2f7e0bd311d19775e49eb3fdd8a93447f1..c5535f4057ddfc40965f27789c34345045c8bf3b 100644
--- a/grp/getgrnam_r.c
+++ b/grp/getgrnam_r.c
@@ -16,13 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/grp-merge.c b/grp/grp-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..a23876fd048013079a186d6a85625ee70340d185
--- /dev/null
+++ b/grp/grp-merge.c
@@ -0,0 +1,185 @@
+/* Group merging implementation.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include "grp-merge.h"
+
+#define BUFCHECK(size)		\
+  do {				\
+    if (c + size > buflen) {	\
+        free (members);		\
+        return ERANGE;		\
+    }				\
+  } while(0)
+
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+{
+  size_t i;
+  size_t c = 0;
+  size_t len;
+  size_t memcount;
+  char **members = NULL;
+
+  /* Copy the GID.  */
+  destgrp->gr_gid = srcgrp.gr_gid;
+
+  /* Copy the name.  */
+  len = sizeof(char) * (strlen (srcgrp.gr_name) + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_name, len);
+  destgrp->gr_name = &destbuf[c];
+  c += len;
+
+  /* Copy the password.  */
+  len = sizeof(char) * (strlen (srcgrp.gr_passwd) + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+  destgrp->gr_passwd = &destbuf[c];
+  c += len;
+
+  /* Count all of the members.  */
+  for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+    ;
+
+  /* Allocate a temporary holding area for the pointers to the member
+     contents, including space for a NULL-terminator.  */
+  members = malloc (sizeof(char *) * (memcount + 1));
+  if (members == NULL)
+    {
+      return ENOMEM;
+    }
+
+  /* Copy all of the group members to destbuf and add a pointer to each of
+     them into the 'members' array.  */
+  for (i = 0; srcgrp.gr_mem[i]; i++)
+    {
+      len = sizeof(char) * (strlen (srcgrp.gr_mem[i]) + 1);
+      BUFCHECK (len);
+      memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+      members[i] = &destbuf[c];
+      c += len;
+    }
+  members[i] = NULL;
+
+  /* Copy the pointers from the members array into the buffer and assign them
+     to the gr_mem member of destgrp.  */
+  destgrp->gr_mem = (char **) &destbuf[c];
+  len = sizeof(char *) * (memcount + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], members, len);
+  c += len;
+  free (members);
+  members = NULL;
+
+  /* Save the count of members at the end.  */
+  BUFCHECK (sizeof(size_t));
+  memcpy (&destbuf[c], &memcount, sizeof(size_t));
+  c += sizeof(size_t);
+
+  if (endptr)
+    {
+      *endptr = destbuf + c;
+    }
+  return 0;
+}
+
+/* Check that the name, GID and passwd fields match, then
+   copy in the gr_mem array.  */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+  size_t c, i, len;
+  size_t savedmemcount;
+  size_t memcount;
+  size_t membersize;
+  char **members = NULL;
+
+  /* We only support merging members of groups with identical names and
+     GID values. If we hit this case, we need to overwrite the current
+     buffer with the saved one (which is functionally equivalent to
+     treating the new lookup as NSS_STATUS NOTFOUND.  */
+  if (mergegrp->gr_gid != savedgrp->gr_gid
+      || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+    {
+      return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+    }
+
+  /* Get the count of group members from the last sizeof(size_t) bytes in the
+     mergegrp buffer.  */
+  savedmemcount = (size_t) *(savedend - sizeof(size_t));
+
+  /* Get the count of new members to add.  */
+  for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+    ;
+
+  /* Create a temporary array to hold the pointers to the member values from
+     both the saved and merge groups.  */
+  membersize = savedmemcount + memcount + 1;
+  members = malloc (sizeof(char *) * membersize);
+  if (members == NULL)
+    {
+      return ENOMEM;
+    }
+
+  /* Copy in the existing member pointers from the saved group
+     Note: this is not NULL-terminated yet.  */
+  memcpy (members, savedgrp->gr_mem, sizeof(char *) * savedmemcount);
+
+  /* Back up into the savedbuf until we get back to the NULL-terminator of the
+     group member list. (This means walking back savedmemcount + 1 (char *) pointers
+     and the member count value.
+     The value of c is going to be the used length of the buffer backed up by
+     the member count and further backed up by the size of the pointers.  */
+  c = savedend - savedbuf
+      - sizeof(size_t)
+      - sizeof(char *) * (savedmemcount + 1);
+
+  /* Add all the new group members, overwriting the old NULL-terminator while
+     adding the new pointers to the temporary array.  */
+  for (i = 0; mergegrp->gr_mem[i]; i++)
+    {
+      len = sizeof(char) * (strlen (mergegrp->gr_mem[i]) + 1);
+      BUFCHECK (len);
+      memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+      members[savedmemcount + i] = &savedbuf[c];
+      c += len;
+    }
+  /* Add the NULL-terminator.  */
+  members[savedmemcount + memcount] = NULL;
+
+  /* Copy the member array back into the buffer after the member list and free
+     the member array.  */
+  savedgrp->gr_mem = (char **) &savedbuf[c];
+  len = sizeof(char *) * membersize;
+  BUFCHECK (len);
+  memcpy (&savedbuf[c], members, len);
+  c += len;
+
+  free (members);
+  members = NULL;
+
+  /* Finally, copy the results back into mergebuf, since that's the buffer
+     that we were provided by the caller.  */
+  return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
diff --git a/grp/grp-merge.h b/grp/grp-merge.h
new file mode 100644
index 0000000000000000000000000000000000000000..7f5f76df574bf8fd9e0a2d7fe5a335f525c767f5
--- /dev/null
+++ b/grp/grp-merge.h
@@ -0,0 +1,40 @@
+/* Group merging implementation.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
+#include <grp.h>
+
+/* __copy_grp:
+   Duplicate a grp struct (and its members).
+   When no longer needed, the calling function
+   must free(newbuf).
+ */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr);
+
+/* __merge_grp:
+   Merge the member lists of two grp structs together.
+ */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf);
+
+#endif /* _GRP_MERGE_H */
diff --git a/manual/nss.texi b/manual/nss.texi
index bf3e69dee537a716f406a54fd74de500b8d97140..f9b71a050265c5764b61e003b359abed68c10131 100644
--- a/manual/nss.texi
+++ b/manual/nss.texi
@@ -178,11 +178,11 @@ where
 @var{action} @result{} return | continue
 @end smallexample
 
 The case of the keywords is insignificant.  The @var{status}
 values are the results of a call to a lookup function of a specific
-service.  They mean
+service.  They mean:
 
 @ftable @samp
 @item success
 No error occurred and the wanted entry is returned.  The default action
 for this is @code{return}.
@@ -202,10 +202,50 @@ The service is temporarily unavailable.  This could mean a file is
 locked or a server currently cannot accept more connections.  The
 default action is @code{continue}.
 @end ftable
 
 @noindent
+The @var{action} values mean:
+
+@ftable @samp
+@item return
+
+If the status matches, stop the lookup process at this service
+specification.  If an entry is available, provide it to the application.
+If an error occurred, report it to the application.  In case of a prior
+@samp{merge} action, the data is combined with previous lookup results,
+as explained below.
+
+@item continue
+
+If the status matches, proceed with the lookup process at the next
+entry, discarding the result of the current lookup (and any merged
+data).  An exception is the @samp{initgroups} database and the
+@samp{success} status, where @samp{continue} acts like @code{merge}
+below.
+
+@item merge
+
+Proceed with the lookup process, retaining the current lookup result.
+This action is useful only with the @samp{success} status.  If a
+subsequent service lookup succeeds and has a matching @samp{return}
+specification, the results are merged, the lookup process ends, and the
+merged results are returned to the application.  If the following service
+has a matching @samp{merge} action, the lookup process continues,
+retaining the combined data from this and any previous lookups.
+
+After a @code{merge} action, errors from subsequent lookups are ignored,
+and the data gathered so far will be returned.
+
+The @samp{merge} only applies to the @samp{success} status.  It is
+currently implemented for the @samp{group} database and its group
+members field, @samp{gr_mem}.  If specified for other databases, it
+causes the lookup to fail (if the @var{status} matches).
+
+@end ftable
+
+@noindent
 If we have a line like
 
 @smallexample
 ethers: nisplus [NOTFOUND=return] db files
 @end smallexample
diff --git a/nscd/Makefile b/nscd/Makefile
index e1a1aa92fc699aa132f7192da49f698a078e5910..3e6895573ae33221c728617f4c95bb3e8c5d5c47 100644
--- a/nscd/Makefile
+++ b/nscd/Makefile
@@ -29,16 +29,19 @@ aux	:= nscd_helper
 endif
 
 # To find xmalloc.c
 vpath %.c ../locale/programs
 
+# To find grp-merge.c
+vpath %.c ../grp
+
 nscd-modules := nscd connections pwdcache getpwnam_r getpwuid_r grpcache \
 		getgrnam_r getgrgid_r hstcache gethstbyad_r gethstbynm3_r \
 		getsrvbynm_r getsrvbypt_r servicescache \
 		dbg_log nscd_conf nscd_stat cache mem nscd_setup_thread \
 		xmalloc xstrdup aicache initgrcache gai res_hconf \
-		netgroupcache
+		netgroupcache grp-merge
 
 ifeq ($(build-nscd)$(have-thread-library),yesyes)
 
 others += nscd
 others-pie += nscd
diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c
index fe5bda424169d56f642f125ef1f2df77a84de221..25de4a3b0b74841c44844a0541cf4d2365b22515 100644
--- a/nscd/getgrgid_r.c
+++ b/nscd/getgrgid_r.c
@@ -15,17 +15,21 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c
index 5ec56877f5798ca34c2e0074d5093cc22b6d58dc..386d66c5832ffee68e95195f6e34b723f41d0984 100644
--- a/nscd/getgrnam_r.c
+++ b/nscd/getgrnam_r.c
@@ -15,16 +15,20 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 198f8cfebd51be9c738f03f950f093b0855ab3cb..49148e8e213af8871c6fbbb265f26e571e0dfec0 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -129,10 +129,56 @@
 # define AF_VAL af
 #else
 # define AF_VAL AF_INET
 #endif
 
+
+/* Set defaults for merge functions that haven't been defined.  */
+#ifndef DEEPCOPY_FN
+static inline int
+__copy_einval(LOOKUP_TYPE a,
+	      const size_t b,
+	      LOOKUP_TYPE *c,
+	      char *d,
+	      char **e)
+{
+  return EINVAL;
+}
+# define DEEPCOPY_FN __copy_einval
+#endif
+
+#ifndef MERGE_FN
+static inline int
+__merge_einval(LOOKUP_TYPE *a,
+	       char *b,
+	       char *c,
+	       size_t d,
+	       LOOKUP_TYPE *e,
+	       char *f)
+{
+  return EINVAL;
+}
+# define MERGE_FN __merge_einval
+#endif
+
+#define CHECK_MERGE(err, status)	\
+do {					\
+  if (err)				\
+    {					\
+      __set_errno (err);		\
+      if (err == ERANGE)		\
+        {				\
+          status = NSS_STATUS_TRYAGAIN;	\
+      }					\
+      else				\
+        {				\
+          status = NSS_STATUS_UNAVAIL;	\
+        }				\
+      break;				\
+    }					\
+} while(0)
+
 /* Type of the lookup function we need here.  */
 typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *,
 					    size_t, int * H_ERRNO_PARM
 					    EXTRA_PARAMS);
 
@@ -150,17 +196,20 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 {
   static bool startp_initialized;
   static service_user *startp;
   static lookup_function start_fct;
   service_user *nip;
+  int do_merge = 0;
+  LOOKUP_TYPE mergegrp;
+  char *mergebuf = NULL;
+  char *endptr = NULL;
   union
   {
     lookup_function l;
     void *ptr;
   } fct;
-
-  int no_more;
+  int no_more, err;
   enum nss_status status = NSS_STATUS_UNAVAIL;
 #ifdef USE_NSCD
   int nscd_status;
 #endif
 #ifdef NEED_H_ERRNO
@@ -276,13 +325,69 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && *h_errnop == NETDB_INTERNAL
 #endif
 	  && errno == ERANGE)
 	break;
 
+      if (do_merge)
+	{
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+		/* The previous loop saved a buffer for merging.
+		   Perform the merge now.  */
+		err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
+				buffer);
+		CHECK_MERGE (err,status);
+		do_merge = 0;
+	    }
+	  else
+	    {
+	      /* If the result wasn't SUCCESS, copy the saved buffer back
+	         into the result buffer and set the status back to
+	         NSS_STATUS_SUCCESS to match the previous pass through the loop.
+	          * If the next action is CONTINUE, it will overwrite the value
+	            currently in the buffer and return the new value.
+	          * If the next action is RETURN, we'll return the previously-
+	            acquired values.
+	          * If the next action is MERGE, then it will be added to the buffer
+	            saved from the previous source.  */
+	      err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL);
+	      CHECK_MERGE (err, status);
+	      status = NSS_STATUS_SUCCESS;
+	    }
+	}
+
+      /* If we were are configured to merge this value with the next one,
+         save the current value of the group struct.  */
+      if (nss_next_action (nip, status) == NSS_ACTION_MERGE
+	  && status == NSS_STATUS_SUCCESS)
+	{
+	  /* Copy the current values into a buffer to be merged with the next
+	     set of retrieved values.  */
+	  if (!mergebuf)
+	    {
+	      /* Only allocate once and reuse it for as many merges as we need
+	         to perform.  */
+	      mergebuf = malloc (buflen);
+	      if (!mergebuf)
+		{
+		  __set_errno (ENOMEM);
+		  status = NSS_STATUS_UNAVAIL;
+		  break;
+		}
+	    }
+
+	  err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr);
+	  CHECK_MERGE (err, status);
+	  do_merge = 1;
+	}
+
       no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING,
 			     REENTRANT2_NAME_STRING, &fct.ptr, status, 0);
     }
+  free(mergebuf);
+  mergebuf = NULL;
 
 #ifdef HANDLE_DIGITS_DOTS
 done:
 #endif
   *result = status == NSS_STATUS_SUCCESS ? resbuf : NULL;
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index f5b903671ca53ccad108eeb4e49ea40a45fa5cdf..1d421ea19868fb0e9b00ec5b3b86e5958c681ab0 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -77,11 +77,26 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       if (stayopen_tmp)
 	status = DL_CALL_FCT (fct.f, (*stayopen_tmp));
       else
 	status = DL_CALL_FCT (fct.f, (0));
 
-      no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	{
+	  /* This is a special-case. When [SUCCESS=merge] is in play,
+	     _nss_next2() will skip to the next database.  Due to the
+	     implementation of that function, we can't know whether we're
+	     in an enumeration or an individual lookup, which behaves
+	     differently with regards to merging.  We'll treat SUCCESS as
+	     an indication to start the enumeration at this database.
+	   */
+	  no_more = 1;
+	}
+      else
+	{
+	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+	}
+
       if (is_last_nip)
 	*last_nip = *nip;
     }
 
   if (stayopen_tmp)
@@ -173,12 +188,27 @@ __nss_getent_r (const char *getent_func_name,
 	  && errno == ERANGE)
 	break;
 
       do
 	{
-	  no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
-				 status, 0);
+	  if (status == NSS_STATUS_SUCCESS
+	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	    {
+	      /* This is a special-case. When [SUCCESS=merge] is in play,
+	         _nss_next2() will skip to the next database.  Due to the
+	         implementation of that function, we can't know whether we're
+	         in an enumeration or an individual lookup, which behaves
+	         differently with regards to merging.  We'll treat SUCCESS as
+	         an indication to return the results here.
+	       */
+	      no_more = 1;
+	    }
+	  else
+	    {
+	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
+				     status, 0);
+	    }
 
 	  if (is_last_nip)
 	    *last_nip = *nip;
 
 	  if (! no_more)
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index faf9d1a0d5680aa79e88b2dfeea18da371c336fb..f8f60ba05aad9a571f928a3ea7d3b14f908ccb2d 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -710,10 +710,13 @@ nss_parse_service_list (const char *line)
 	      if (line - name == 6 && __strncasecmp (name, "RETURN", 6) == 0)
 		action = NSS_ACTION_RETURN;
 	      else if (line - name == 8
 		       && __strncasecmp (name, "CONTINUE", 8) == 0)
 		action = NSS_ACTION_CONTINUE;
+	      else if (line - name == 5
+		       && __strncasecmp (name, "MERGE", 5) == 0)
+		action = NSS_ACTION_MERGE;
 	      else
 		goto finish;
 
 	      if (not)
 		{
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index a5318fa82be43c8314807ad76de231e572e91c06..5bc2de3b1d82978102ac7a129c0ba5b7eb3cfd25 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -30,11 +30,12 @@
 
 /* Actions performed after lookup finished.  */
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;
 
 
 typedef struct service_library
 {
-- 
2.5.0

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2015-12-16 15:11 [PATCH] nsswitch: Add group merging support Stephen Gallagher
@ 2015-12-29 21:58 ` Mike Frysinger
  2016-01-04 14:10   ` Stephen Gallagher
                     ` (2 more replies)
  2016-03-28 13:14 ` Florian Weimer
  1 sibling, 3 replies; 33+ messages in thread
From: Mike Frysinger @ 2015-12-29 21:58 UTC (permalink / raw)
  To: Stephen Gallagher; +Cc: libc-alpha

[-- Attachment #1: Type: text/plain, Size: 3222 bytes --]

On 16 Dec 2015 10:11, Stephen Gallagher wrote:
> == Justification ==
> It is common today for users to rely on centrally-managed user stores for
> handling their user accounts. However, much software existing today does
> not have an innate understanding of such accounts. Instead, they commonly
> rely on membership in known groups for managing access-control (for
> example the "wheel" group on Fedora and RHEL systems or the "adm" group
> on Debian-derived systems). In the present incarnation of nsswitch, the
> only way to have such groups managed by a remote user store such as
> FreeIPA or Active Directory would be to manually remove the groups from
> /etc/group on the clients so that nsswitch would then move past nss_files
> and into the SSSD, nss-ldap or other remote user database.

you've lost me.  the whole point of nsswitch.conf is to let the admin
explicitly control the order and precedence of look up sources.  so if
you want to look up other sources, fix your /etc/nsswitch.conf to list
the remote sources first over /etc/groups.

> == Solution ==
> With this patch, a new action is introduced for nsswitch:
> NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
> between two database entries in the nsswitch.conf file. When a group is
> located in the first of the two group entries, processing will continue
> on to the next one. If the group is also found in the next entry (and the
> group name and GID are an exact match), the member list of the second
> entry will be added to the group object to be returned.

what if group/gid do not match ?  the edge cases must be explicitly
documented in the manual.

> I performed testing by running the getent utility against my newly-built
> glibc and configuring /etc/nsswitch.conf with the following entry:

what will it take to get tests into glibc itself ?  ad-hoc testing is
a great way for code to rot.

> +  len = sizeof(char) * (strlen (srcgrp.gr_name) + 1);

space before the ( ... this comes up a few times in this func

> +  if (members == NULL)
> +    {
> +      return ENOMEM;
> +    }

no need for braces in single statements like this.  comes up a few times
in your patch.

> --- /dev/null
> +++ b/grp/grp-merge.h
> @@ -0,0 +1,40 @@
>
> +/* __copy_grp:
> +   Duplicate a grp struct (and its members).
> +   When no longer needed, the calling function
> +   must free(newbuf).
> + */

the comment style is incorrect.  should be:
/* Duplicate a grp struct (and its members).  When no longer needed, the calling
   function must free the newbuf.  */

> +/* __merge_grp:
> +   Merge the member lists of two grp structs together.
> + */

same here

> --- a/nss/getXXbyYY_r.c
> +++ b/nss/getXXbyYY_r.c
>
> +__copy_einval(LOOKUP_TYPE a,

space before the (

> +__merge_einval(LOOKUP_TYPE *a,

same here

> +#define CHECK_MERGE(err, status)	\
> +do {					\
> +  if (err)				\
> +    {					\
> +      __set_errno (err);		\
> +      if (err == ERANGE)		\
> +        {				\
> +          status = NSS_STATUS_TRYAGAIN;	\
> +      }					\
> +      else				\
> +        {				\
> +          status = NSS_STATUS_UNAVAIL;	\
> +        }				\

indentation is wrong w/braces in the inside if block, but you should
just drop them all
-mike

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2015-12-29 21:58 ` Mike Frysinger
@ 2016-01-04 14:10   ` Stephen Gallagher
  2016-01-04 14:12     ` Stephen Gallagher
  2016-01-13  1:24   ` Carlos O'Donell
  2016-01-20 20:32   ` Carlos O'Donell
  2 siblings, 1 reply; 33+ messages in thread
From: Stephen Gallagher @ 2016-01-04 14:10 UTC (permalink / raw)
  To: libc-alpha

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 12/29/2015 04:58 PM, Mike Frysinger wrote:
> On 16 Dec 2015 10:11, Stephen Gallagher wrote:
>> == Justification == It is common today for users to rely on
>> centrally-managed user stores for handling their user accounts.
>> However, much software existing today does not have an innate
>> understanding of such accounts. Instead, they commonly rely on
>> membership in known groups for managing access-control (for 
>> example the "wheel" group on Fedora and RHEL systems or the "adm"
>> group on Debian-derived systems). In the present incarnation of
>> nsswitch, the only way to have such groups managed by a remote
>> user store such as FreeIPA or Active Directory would be to
>> manually remove the groups from /etc/group on the clients so that
>> nsswitch would then move past nss_files and into the SSSD,
>> nss-ldap or other remote user database.
> 
> you've lost me.  the whole point of nsswitch.conf is to let the
> admin explicitly control the order and precedence of look up
> sources.  so if you want to look up other sources, fix your
> /etc/nsswitch.conf to list the remote sources first over
> /etc/groups.
> 

I think I did lose you, yes. The fundamental problem here is that
glibc/nsswitch assumes that one and only one database in the list is
always authoritative. This does not match reality. The most common
example is the 'wheel' group on Red Hat-derived systems or the 'adm'
group on Debian-derived systems. These groups are shipped by default
in the installation and may have users added to them by the OS
installer (for example, it is very common for an OS installer to add a
local system administrator; someone who has infinite sudo privilege).

However, in many non-single-user systems this machine will later be
added to a domain such as FreeIPA or Active Directory. In this
situation, it is rarely desirable that the local administrators be
locked out of the machine by ordering the domain groups before the
local groups. (In particular, this can make debugging of domain client
issues difficult or impossible if there is no root password, such as
on Ubuntu).

The purpose of this patch is to allow group memberships to be merged,
such that domain members are added as supplemental to the local members.


>> == Solution == With this patch, a new action is introduced for
>> nsswitch: NSS_ACTION_MERGE. To take advantage of it, one will add
>> [SUCCESS=merge] between two database entries in the nsswitch.conf
>> file. When a group is located in the first of the two group
>> entries, processing will continue on to the next one. If the
>> group is also found in the next entry (and the group name and GID
>> are an exact match), the member list of the second entry will be
>> added to the group object to be returned.
> 
> what if group/gid do not match ?  the edge cases must be
> explicitly documented in the manual.
> 

Huh, odd. I think somehow I accidentally dropped that paragraph during
editing. I did originally state that it was explicitly an undefined
result (in reality, the merging code checks this and treats the second
entry safely as NOTFOUND, but this shouldn't be documented as API
since it may still have unexpected and different behavior between
getgrgid() and getgrnam() lookups)

I'll submit a corrected manpage as a follow-up to this email.



>> I performed testing by running the getent utility against my
>> newly-built glibc and configuring /etc/nsswitch.conf with the
>> following entry:
> 
> what will it take to get tests into glibc itself ?  ad-hoc testing
> is a great way for code to rot.
> 

I'm working with Carlos O'Donnell to develop tests for this in
parallel. I agree, we don't want this to bitrot. We decided that it
wasn't strictly necessary to submit them at the same time.


>> +  len = sizeof(char) * (strlen (srcgrp.gr_name) + 1);
> 
> space before the ( ... this comes up a few times in this func
> 

Sorry, that was ambiguous. Were you telling me that it should be:

len = sizeof (char) * (strlen (srcgrp.gr_name) + 1);
or
len = sizeof(char) * (strlen(srcgrp.gr_name) + 1);


I'm guessing it is supposed to be the former and will resubmit the
patch accordingly.


>> +  if (members == NULL) +    { +      return ENOMEM; +    }
> 
> no need for braces in single statements like this.  comes up a few
> times in your patch.
> 

This is a personal habit of mine; In the past, I've had many bugs
creep into code due to a patch merge resulting in an un-bracketed if
statement with two lines. (A popular example of this would be Apple's
goto fail; bug from a while back).

So I always include braces for future peace of mind. If glibc insists
upon this for aesthetic reasons, I will acquiesce, but I don't really
understand it.


>> --- /dev/null +++ b/grp/grp-merge.h @@ -0,0 +1,40 @@
>> 
>> +/* __copy_grp: +   Duplicate a grp struct (and its members). +
>> When no longer needed, the calling function +   must
>> free(newbuf). + */
> 
> the comment style is incorrect.  should be: /* Duplicate a grp
> struct (and its members).  When no longer needed, the calling 
> function must free the newbuf.  */
> 

Fixed.

>> +/* __merge_grp: +   Merge the member lists of two grp structs
>> together. + */
> 
> same here
> 

Fixed.

>> --- a/nss/getXXbyYY_r.c +++ b/nss/getXXbyYY_r.c
>> 
>> +__copy_einval(LOOKUP_TYPE a,
> 
> space before the (
> 

Fixed.

>> +__merge_einval(LOOKUP_TYPE *a,
> 
> same here
> 

Fixed.

>> +#define CHECK_MERGE(err, status)	\ +do {					\ +  if (err)				\ 
>> +    {					\ +      __set_errno (err);		\ +      if (err ==
>> ERANGE)		\ +        {				\ +          status =
>> NSS_STATUS_TRYAGAIN;	\ +      }					\ +      else				\ +        {
>> \ +          status = NSS_STATUS_UNAVAIL;	\ +        }				\
> 
> indentation is wrong w/braces in the inside if block, but you
> should just drop them all -mike
> 

Fixed
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iEYEARECAAYFAlaKfTYACgkQeiVVYja6o6PFXwCfV9WT/JUfWM8mUdNQxDOh51NE
pxcAmwXYg7rvmC/MXslFwOuVRabmVVDB
=Kpaw
-----END PGP SIGNATURE-----

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH] nsswitch: Add group merging support
  2016-01-04 14:10   ` Stephen Gallagher
@ 2016-01-04 14:12     ` Stephen Gallagher
  2016-01-04 15:42       ` Joseph Myers
  2016-03-22  0:40       ` Mike Frysinger
  0 siblings, 2 replies; 33+ messages in thread
From: Stephen Gallagher @ 2016-01-04 14:12 UTC (permalink / raw)
  To: libc-alpha

https://sourceware.org/glibc/wiki/Proposals/GroupMerging

== Justification ==
It is common today for users to rely on centrally-managed user stores for
handling their user accounts. However, much software existing today does
not have an innate understanding of such accounts. Instead, they commonly
rely on membership in known groups for managing access-control (for
example the "wheel" group on Fedora and RHEL systems or the "adm" group
on Debian-derived systems). In the present incarnation of nsswitch, the
only way to have such groups managed by a remote user store such as
FreeIPA or Active Directory would be to manually remove the groups from
/etc/group on the clients so that nsswitch would then move past nss_files
and into the SSSD, nss-ldap or other remote user database.

== Solution ==
With this patch, a new action is introduced for nsswitch:
NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
between two database entries in the nsswitch.conf file. When a group is
located in the first of the two group entries, processing will continue
on to the next one. If the group is also found in the next entry (and the
group name and GID are an exact match), the member list of the second
entry will be added to the group object to be returned.

== Implementation ==
After each DL_LOOKUP_FN() returns, the next action is checked. If the
function returned NSS_STATUS_SUCCESS and the next action is
NSS_ACTION_MERGE, a copy of the result buffer is saved for the next pass
through the loop. If on this next pass through the loop the database
returns another instance of a group matching both the group name and GID,
the member list is added to the previous list and it is returned as a
single object. If the following database does not contain the same group,
then the original is copied back into the destination buffer.

This patch implements merge functionality only for the group database.
For other databases, there is a default implementation that will return
the EINVAL errno if a merge is requested. The merge functionality can be
implemented for other databases at a later time if such is needed. Each
database must provide a unique implementation of the deep-copy and merge
functions.

If [SUCCESS=merge] is present in nsswitch.conf for a glibc version that
does not support it, glibc will process results up until that operation,
at which time it will return results if it has found them or else will
simply return an error. In practical terms, this ends up behaving like
the remainder of the nsswitch.conf line does not exist.

== Iterators ==
This feature does not modify the iterator functionality from its current
behavior. If getgrnam() or getgrgid() is called, glibc will iterate
through all entries in the `group` line in nsswitch.conf and display the
list of members without attempting to merge them. This is consistent with
the behavior of nss_files where if two separate lines are specified for
the same group in /etc/groups, getgrnam()/getgrgid() will display both.
Clients are already expected to handle this gracefully.

== No Premature Optimizations ==
The following is a list of places that might be eligible for
optimization, but were not overengineered for this initial contribution:
 * Any situation where a merge may occur will result in one malloc() of
   the same size as the input buffer.
 * Any situation where a merge does occur will result in a second
   malloc() to hold the list of pointers to member name strings.
 * The list of members is simply concatenated together and is not tested
   for uniqueness (which is identical to the behavior for nss_files,
   which will simply return identical values if they both exist on the
   line in the file. This could potentially be optimized to reduce space
   usage in the buffer, but it is both complex and computationally
   expensive to do so.

== Testing ==
I performed testing by running the getent utility against my newly-built
glibc and configuring /etc/nsswitch.conf with the following entry:
group: group:      files [SUCCESS=merge] sss

In /etc/group I included the line:
wheel:x:10:sgallagh

I then configured my local SSSD using the id_provider=local to respond
with:
wheel:*:10:localuser,localuser2

I then ran `getent group wheel` against the newly-built glibc in
multiple situations and received the expected output as described
above:
 * When SSSD was running.
 * When SSSD was configured in nsswitch.conf but the daemon was not
   running.
 * When SSSD was configured in nsswitch.conf but nss_sss.so.2 was not
   installed on the system.
 * When the order of 'sss' and 'files' was reversed.
 * All of the above with the [SUCCESS=merge] removed (to ensure no
   regressions).
 * All of the above with `getent group 10`.
 * All of the above with `getent group` with and without
   `enumerate=true` set in SSSD.
 * All of the above with and without nscd enabled on the system.

== NEWS ==

* A new NSS action is added to facilitate large distribution system
  administration.  The action, MERGE, allows remote user stores like
  LDAP to be merged into local user stores like /etc/groups in order
  to provide easy to use, updated, and managed sets of merged
  credentials.  The new action can be used by configuring it in
  /etc/nsswitch.conf:
  group: files [SUCCESS=merge] nis
  Implemented by Stephen Gallagher (Red Hat).

== ChangeLog ==

2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>

	[BZ #19072]
	* grp/Makefile (headers): Add grp-merge.h
	(routines): Add grp-merge.
	* grp/getgrgid_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/getgrname_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/grp-merge.c: New file.
	* grp/grp-merge.h: New file.
	* manual/nss.texi (Actions in the NSS configuration): Describe
	return, continue, and merge.
	* nscd/Makefile: Add vpath to find grp-merge.c
	(nscd-modules): Add grp-merge.
	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
	[!MERGE_FN]: Define __merge_einval.
	(CHECK_MERGE): Define.
	(REENTRANT_NAME): Process merge if do_merge is true.
	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
	(__nss_getent_r): Likewise.
	* nss/nsswitch.c (nss_parse_service_list): Likewise.
	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

Resolves BZ #19072
---
 grp/Makefile      |   5 +-
 grp/getgrgid_r.c  |   4 ++
 grp/getgrnam_r.c  |   4 ++
 grp/grp-merge.c   | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 grp/grp-merge.h   |  35 +++++++++++
 manual/nss.texi   |  46 +++++++++++++-
 nscd/Makefile     |   5 +-
 nscd/getgrgid_r.c |   4 ++
 nscd/getgrnam_r.c |   4 ++
 nss/getXXbyYY_r.c | 105 +++++++++++++++++++++++++++++++-
 nss/getnssent_r.c |  36 ++++++++++-
 nss/nsswitch.c    |   3 +
 nss/nsswitch.h    |   3 +-
 13 files changed, 421 insertions(+), 10 deletions(-)
 create mode 100644 grp/grp-merge.c
 create mode 100644 grp/grp-merge.h

diff --git a/grp/Makefile b/grp/Makefile
index ed8cc2b0564f0e3842cd78f24a4e0788d659bbc4..52af992365268aae8cf8a80cd7216160b1431e84 100644
--- a/grp/Makefile
+++ b/grp/Makefile
@@ -20,15 +20,16 @@
 #
 subdir	:= grp
 
 include ../Makeconfig
 
-headers := grp.h
+headers := grp.h grp-merge.h
 
 routines := fgetgrent initgroups setgroups \
 	    getgrent getgrgid getgrnam putgrent \
-	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r
+	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r \
+	    grp-merge
 
 tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
index 05d4d772d3ef0bfae8f9375387c41310885ce41a..5bd32b9881e54a46262df668542d5f925c44dd93 100644
--- a/grp/getgrgid_r.c
+++ b/grp/getgrgid_r.c
@@ -16,14 +16,18 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
+
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
 
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c
index 0061cb2f7e0bd311d19775e49eb3fdd8a93447f1..c5535f4057ddfc40965f27789c34345045c8bf3b 100644
--- a/grp/getgrnam_r.c
+++ b/grp/getgrnam_r.c
@@ -16,13 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/grp-merge.c b/grp/grp-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..63229856616cda4471400b8f8362c877d504a062
--- /dev/null
+++ b/grp/grp-merge.c
@@ -0,0 +1,177 @@
+/* Group merging implementation.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include "grp-merge.h"
+
+#define BUFCHECK(size)		\
+  do {				\
+    if (c + size > buflen) {	\
+        free (members);		\
+        return ERANGE;		\
+    }				\
+  } while(0)
+
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+{
+  size_t i;
+  size_t c = 0;
+  size_t len;
+  size_t memcount;
+  char **members = NULL;
+
+  /* Copy the GID.  */
+  destgrp->gr_gid = srcgrp.gr_gid;
+
+  /* Copy the name.  */
+  len = sizeof (char) * (strlen (srcgrp.gr_name) + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_name, len);
+  destgrp->gr_name = &destbuf[c];
+  c += len;
+
+  /* Copy the password.  */
+  len = sizeof (char) * (strlen (srcgrp.gr_passwd) + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+  destgrp->gr_passwd = &destbuf[c];
+  c += len;
+
+  /* Count all of the members.  */
+  for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+    ;
+
+  /* Allocate a temporary holding area for the pointers to the member
+     contents, including space for a NULL-terminator.  */
+  members = malloc (sizeof (char *) * (memcount + 1));
+  if (members == NULL)
+      return ENOMEM;
+
+  /* Copy all of the group members to destbuf and add a pointer to each of
+     them into the 'members' array.  */
+  for (i = 0; srcgrp.gr_mem[i]; i++)
+    {
+      len = sizeof (char) * (strlen (srcgrp.gr_mem[i]) + 1);
+      BUFCHECK (len);
+      memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+      members[i] = &destbuf[c];
+      c += len;
+    }
+  members[i] = NULL;
+
+  /* Copy the pointers from the members array into the buffer and assign them
+     to the gr_mem member of destgrp.  */
+  destgrp->gr_mem = (char **) &destbuf[c];
+  len = sizeof (char *) * (memcount + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], members, len);
+  c += len;
+  free (members);
+  members = NULL;
+
+  /* Save the count of members at the end.  */
+  BUFCHECK (sizeof (size_t));
+  memcpy (&destbuf[c], &memcount, sizeof (size_t));
+  c += sizeof (size_t);
+
+  if (endptr)
+      *endptr = destbuf + c;
+  return 0;
+}
+
+/* Check that the name, GID and passwd fields match, then
+   copy in the gr_mem array.  */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+  size_t c, i, len;
+  size_t savedmemcount;
+  size_t memcount;
+  size_t membersize;
+  char **members = NULL;
+
+  /* We only support merging members of groups with identical names and
+     GID values. If we hit this case, we need to overwrite the current
+     buffer with the saved one (which is functionally equivalent to
+     treating the new lookup as NSS_STATUS NOTFOUND.  */
+  if (mergegrp->gr_gid != savedgrp->gr_gid
+      || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+      return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+
+  /* Get the count of group members from the last sizeof (size_t) bytes in the
+     mergegrp buffer.  */
+  savedmemcount = (size_t) *(savedend - sizeof (size_t));
+
+  /* Get the count of new members to add.  */
+  for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+    ;
+
+  /* Create a temporary array to hold the pointers to the member values from
+     both the saved and merge groups.  */
+  membersize = savedmemcount + memcount + 1;
+  members = malloc (sizeof (char *) * membersize);
+  if (members == NULL)
+      return ENOMEM;
+
+  /* Copy in the existing member pointers from the saved group
+     Note: this is not NULL-terminated yet.  */
+  memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount);
+
+  /* Back up into the savedbuf until we get back to the NULL-terminator of the
+     group member list. (This means walking back savedmemcount + 1 (char *) pointers
+     and the member count value.
+     The value of c is going to be the used length of the buffer backed up by
+     the member count and further backed up by the size of the pointers.  */
+  c = savedend - savedbuf
+      - sizeof (size_t)
+      - sizeof (char *) * (savedmemcount + 1);
+
+  /* Add all the new group members, overwriting the old NULL-terminator while
+     adding the new pointers to the temporary array.  */
+  for (i = 0; mergegrp->gr_mem[i]; i++)
+    {
+      len = sizeof (char) * (strlen (mergegrp->gr_mem[i]) + 1);
+      BUFCHECK (len);
+      memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+      members[savedmemcount + i] = &savedbuf[c];
+      c += len;
+    }
+  /* Add the NULL-terminator.  */
+  members[savedmemcount + memcount] = NULL;
+
+  /* Copy the member array back into the buffer after the member list and free
+     the member array.  */
+  savedgrp->gr_mem = (char **) &savedbuf[c];
+  len = sizeof (char *) * membersize;
+  BUFCHECK (len);
+  memcpy (&savedbuf[c], members, len);
+  c += len;
+
+  free (members);
+  members = NULL;
+
+  /* Finally, copy the results back into mergebuf, since that's the buffer
+     that we were provided by the caller.  */
+  return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
diff --git a/grp/grp-merge.h b/grp/grp-merge.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e901cd5ea538089536932b69d2ac8a9e55fc983
--- /dev/null
+++ b/grp/grp-merge.h
@@ -0,0 +1,35 @@
+/* Group merging implementation.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
+#include <grp.h>
+
+/* Duplicate a grp struct (and its members). When no longer needed, the
+   calling function must free(newbuf). */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr);
+
+/* Merge the member lists of two grp structs together. */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf);
+
+#endif /* _GRP_MERGE_H */
diff --git a/manual/nss.texi b/manual/nss.texi
index 66dcceffe01f225f078e88dd006bb90e80c85723..95e3544bcd97995720e7154ec43df4090154bf4c 100644
--- a/manual/nss.texi
+++ b/manual/nss.texi
@@ -178,11 +178,11 @@ where
 @var{action} @result{} return | continue
 @end smallexample
 
 The case of the keywords is insignificant.  The @var{status}
 values are the results of a call to a lookup function of a specific
-service.  They mean
+service.  They mean:
 
 @ftable @samp
 @item success
 No error occurred and the wanted entry is returned.  The default action
 for this is @code{return}.
@@ -202,10 +202,54 @@ The service is temporarily unavailable.  This could mean a file is
 locked or a server currently cannot accept more connections.  The
 default action is @code{continue}.
 @end ftable
 
 @noindent
+The @var{action} values mean:
+
+@ftable @samp
+@item return
+
+If the status matches, stop the lookup process at this service
+specification.  If an entry is available, provide it to the application.
+If an error occurred, report it to the application.  In case of a prior
+@samp{merge} action, the data is combined with previous lookup results,
+as explained below.
+
+@item continue
+
+If the status matches, proceed with the lookup process at the next
+entry, discarding the result of the current lookup (and any merged
+data).  An exception is the @samp{initgroups} database and the
+@samp{success} status, where @samp{continue} acts like @code{merge}
+below.
+
+@item merge
+
+Proceed with the lookup process, retaining the current lookup result.
+This action is useful only with the @samp{success} status.  If a
+subsequent service lookup succeeds and has a matching @samp{return}
+specification, the results are merged, the lookup process ends, and the
+merged results are returned to the application.  If the following service
+has a matching @samp{merge} action, the lookup process continues,
+retaining the combined data from this and any previous lookups.
+
+After a @code{merge} action, errors from subsequent lookups are ignored,
+and the data gathered so far will be returned.
+
+The @samp{merge} only applies to the @samp{success} status.  It is
+currently implemented for the @samp{group} database and its group
+members field, @samp{gr_mem}.  If specified for other databases, it
+causes the lookup to fail (if the @var{status} matches).
+
+When processing @samp{merge} for @samp{group} membership, the group GID
+and name must be identical for both entries. If only one or the other is
+a match, the behavior is undefined.
+
+@end ftable
+
+@noindent
 If we have a line like
 
 @smallexample
 ethers: nisplus [NOTFOUND=return] db files
 @end smallexample
diff --git a/nscd/Makefile b/nscd/Makefile
index e1a1aa92fc699aa132f7192da49f698a078e5910..3e6895573ae33221c728617f4c95bb3e8c5d5c47 100644
--- a/nscd/Makefile
+++ b/nscd/Makefile
@@ -29,16 +29,19 @@ aux	:= nscd_helper
 endif
 
 # To find xmalloc.c
 vpath %.c ../locale/programs
 
+# To find grp-merge.c
+vpath %.c ../grp
+
 nscd-modules := nscd connections pwdcache getpwnam_r getpwuid_r grpcache \
 		getgrnam_r getgrgid_r hstcache gethstbyad_r gethstbynm3_r \
 		getsrvbynm_r getsrvbypt_r servicescache \
 		dbg_log nscd_conf nscd_stat cache mem nscd_setup_thread \
 		xmalloc xstrdup aicache initgrcache gai res_hconf \
-		netgroupcache
+		netgroupcache grp-merge
 
 ifeq ($(build-nscd)$(have-thread-library),yesyes)
 
 others += nscd
 others-pie += nscd
diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c
index fe5bda424169d56f642f125ef1f2df77a84de221..25de4a3b0b74841c44844a0541cf4d2365b22515 100644
--- a/nscd/getgrgid_r.c
+++ b/nscd/getgrgid_r.c
@@ -15,17 +15,21 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c
index 5ec56877f5798ca34c2e0074d5093cc22b6d58dc..386d66c5832ffee68e95195f6e34b723f41d0984 100644
--- a/nscd/getgrnam_r.c
+++ b/nscd/getgrnam_r.c
@@ -15,16 +15,20 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 198f8cfebd51be9c738f03f950f093b0855ab3cb..5f49ae8b828fce6dc30c7ccc9606319bffc52d5b 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -129,10 +129,52 @@
 # define AF_VAL af
 #else
 # define AF_VAL AF_INET
 #endif
 
+
+/* Set defaults for merge functions that haven't been defined.  */
+#ifndef DEEPCOPY_FN
+static inline int
+__copy_einval (LOOKUP_TYPE a,
+	      const size_t b,
+	      LOOKUP_TYPE *c,
+	      char *d,
+	      char **e)
+{
+  return EINVAL;
+}
+# define DEEPCOPY_FN __copy_einval
+#endif
+
+#ifndef MERGE_FN
+static inline int
+__merge_einval (LOOKUP_TYPE *a,
+	       char *b,
+	       char *c,
+	       size_t d,
+	       LOOKUP_TYPE *e,
+	       char *f)
+{
+  return EINVAL;
+}
+# define MERGE_FN __merge_einval
+#endif
+
+#define CHECK_MERGE(err, status)	\
+do {					\
+  if (err)				\
+    {					\
+      __set_errno (err);		\
+      if (err == ERANGE)		\
+          status = NSS_STATUS_TRYAGAIN;	\
+      else				\
+          status = NSS_STATUS_UNAVAIL;	\
+      break;				\
+    }					\
+} while(0)
+
 /* Type of the lookup function we need here.  */
 typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *,
 					    size_t, int * H_ERRNO_PARM
 					    EXTRA_PARAMS);
 
@@ -150,17 +192,20 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 {
   static bool startp_initialized;
   static service_user *startp;
   static lookup_function start_fct;
   service_user *nip;
+  int do_merge = 0;
+  LOOKUP_TYPE mergegrp;
+  char *mergebuf = NULL;
+  char *endptr = NULL;
   union
   {
     lookup_function l;
     void *ptr;
   } fct;
-
-  int no_more;
+  int no_more, err;
   enum nss_status status = NSS_STATUS_UNAVAIL;
 #ifdef USE_NSCD
   int nscd_status;
 #endif
 #ifdef NEED_H_ERRNO
@@ -276,13 +321,69 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && *h_errnop == NETDB_INTERNAL
 #endif
 	  && errno == ERANGE)
 	break;
 
+      if (do_merge)
+	{
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+		/* The previous loop saved a buffer for merging.
+		   Perform the merge now.  */
+		err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
+				buffer);
+		CHECK_MERGE (err,status);
+		do_merge = 0;
+	    }
+	  else
+	    {
+	      /* If the result wasn't SUCCESS, copy the saved buffer back
+	         into the result buffer and set the status back to
+	         NSS_STATUS_SUCCESS to match the previous pass through the loop.
+	          * If the next action is CONTINUE, it will overwrite the value
+	            currently in the buffer and return the new value.
+	          * If the next action is RETURN, we'll return the previously-
+	            acquired values.
+	          * If the next action is MERGE, then it will be added to the buffer
+	            saved from the previous source.  */
+	      err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL);
+	      CHECK_MERGE (err, status);
+	      status = NSS_STATUS_SUCCESS;
+	    }
+	}
+
+      /* If we were are configured to merge this value with the next one,
+         save the current value of the group struct.  */
+      if (nss_next_action (nip, status) == NSS_ACTION_MERGE
+	  && status == NSS_STATUS_SUCCESS)
+	{
+	  /* Copy the current values into a buffer to be merged with the next
+	     set of retrieved values.  */
+	  if (!mergebuf)
+	    {
+	      /* Only allocate once and reuse it for as many merges as we need
+	         to perform.  */
+	      mergebuf = malloc (buflen);
+	      if (!mergebuf)
+		{
+		  __set_errno (ENOMEM);
+		  status = NSS_STATUS_UNAVAIL;
+		  break;
+		}
+	    }
+
+	  err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr);
+	  CHECK_MERGE (err, status);
+	  do_merge = 1;
+	}
+
       no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING,
 			     REENTRANT2_NAME_STRING, &fct.ptr, status, 0);
     }
+  free(mergebuf);
+  mergebuf = NULL;
 
 #ifdef HANDLE_DIGITS_DOTS
 done:
 #endif
   *result = status == NSS_STATUS_SUCCESS ? resbuf : NULL;
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index f5b903671ca53ccad108eeb4e49ea40a45fa5cdf..1d421ea19868fb0e9b00ec5b3b86e5958c681ab0 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -77,11 +77,26 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       if (stayopen_tmp)
 	status = DL_CALL_FCT (fct.f, (*stayopen_tmp));
       else
 	status = DL_CALL_FCT (fct.f, (0));
 
-      no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	{
+	  /* This is a special-case. When [SUCCESS=merge] is in play,
+	     _nss_next2() will skip to the next database.  Due to the
+	     implementation of that function, we can't know whether we're
+	     in an enumeration or an individual lookup, which behaves
+	     differently with regards to merging.  We'll treat SUCCESS as
+	     an indication to start the enumeration at this database.
+	   */
+	  no_more = 1;
+	}
+      else
+	{
+	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+	}
+
       if (is_last_nip)
 	*last_nip = *nip;
     }
 
   if (stayopen_tmp)
@@ -173,12 +188,27 @@ __nss_getent_r (const char *getent_func_name,
 	  && errno == ERANGE)
 	break;
 
       do
 	{
-	  no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
-				 status, 0);
+	  if (status == NSS_STATUS_SUCCESS
+	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	    {
+	      /* This is a special-case. When [SUCCESS=merge] is in play,
+	         _nss_next2() will skip to the next database.  Due to the
+	         implementation of that function, we can't know whether we're
+	         in an enumeration or an individual lookup, which behaves
+	         differently with regards to merging.  We'll treat SUCCESS as
+	         an indication to return the results here.
+	       */
+	      no_more = 1;
+	    }
+	  else
+	    {
+	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
+				     status, 0);
+	    }
 
 	  if (is_last_nip)
 	    *last_nip = *nip;
 
 	  if (! no_more)
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index faf9d1a0d5680aa79e88b2dfeea18da371c336fb..f8f60ba05aad9a571f928a3ea7d3b14f908ccb2d 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -710,10 +710,13 @@ nss_parse_service_list (const char *line)
 	      if (line - name == 6 && __strncasecmp (name, "RETURN", 6) == 0)
 		action = NSS_ACTION_RETURN;
 	      else if (line - name == 8
 		       && __strncasecmp (name, "CONTINUE", 8) == 0)
 		action = NSS_ACTION_CONTINUE;
+	      else if (line - name == 5
+		       && __strncasecmp (name, "MERGE", 5) == 0)
+		action = NSS_ACTION_MERGE;
 	      else
 		goto finish;
 
 	      if (not)
 		{
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index a5318fa82be43c8314807ad76de231e572e91c06..5bc2de3b1d82978102ac7a129c0ba5b7eb3cfd25 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -30,11 +30,12 @@
 
 /* Actions performed after lookup finished.  */
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;
 
 
 typedef struct service_library
 {
-- 
2.5.0

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-01-04 14:12     ` Stephen Gallagher
@ 2016-01-04 15:42       ` Joseph Myers
  2016-01-04 17:40         ` Stephen Gallagher
  2016-03-22  0:40       ` Mike Frysinger
  1 sibling, 1 reply; 33+ messages in thread
From: Joseph Myers @ 2016-01-04 15:42 UTC (permalink / raw)
  To: Stephen Gallagher; +Cc: libc-alpha

2016 in copyright dates for new files submitted this year, please.  I'll 
do the update for existing files shortly.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH] nsswitch: Add group merging support
  2016-01-04 15:42       ` Joseph Myers
@ 2016-01-04 17:40         ` Stephen Gallagher
  2016-01-06 22:41           ` Andreas Schwab
  0 siblings, 1 reply; 33+ messages in thread
From: Stephen Gallagher @ 2016-01-04 17:40 UTC (permalink / raw)
  To: libc-alpha

https://sourceware.org/glibc/wiki/Proposals/GroupMerging

== Justification ==
It is common today for users to rely on centrally-managed user stores for
handling their user accounts. However, much software existing today does
not have an innate understanding of such accounts. Instead, they commonly
rely on membership in known groups for managing access-control (for
example the "wheel" group on Fedora and RHEL systems or the "adm" group
on Debian-derived systems). In the present incarnation of nsswitch, the
only way to have such groups managed by a remote user store such as
FreeIPA or Active Directory would be to manually remove the groups from
/etc/group on the clients so that nsswitch would then move past nss_files
and into the SSSD, nss-ldap or other remote user database.

== Solution ==
With this patch, a new action is introduced for nsswitch:
NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
between two database entries in the nsswitch.conf file. When a group is
located in the first of the two group entries, processing will continue
on to the next one. If the group is also found in the next entry (and the
group name and GID are an exact match), the member list of the second
entry will be added to the group object to be returned.

== Implementation ==
After each DL_LOOKUP_FN() returns, the next action is checked. If the
function returned NSS_STATUS_SUCCESS and the next action is
NSS_ACTION_MERGE, a copy of the result buffer is saved for the next pass
through the loop. If on this next pass through the loop the database
returns another instance of a group matching both the group name and GID,
the member list is added to the previous list and it is returned as a
single object. If the following database does not contain the same group,
then the original is copied back into the destination buffer.

This patch implements merge functionality only for the group database.
For other databases, there is a default implementation that will return
the EINVAL errno if a merge is requested. The merge functionality can be
implemented for other databases at a later time if such is needed. Each
database must provide a unique implementation of the deep-copy and merge
functions.

If [SUCCESS=merge] is present in nsswitch.conf for a glibc version that
does not support it, glibc will process results up until that operation,
at which time it will return results if it has found them or else will
simply return an error. In practical terms, this ends up behaving like
the remainder of the nsswitch.conf line does not exist.

== Iterators ==
This feature does not modify the iterator functionality from its current
behavior. If getgrnam() or getgrgid() is called, glibc will iterate
through all entries in the `group` line in nsswitch.conf and display the
list of members without attempting to merge them. This is consistent with
the behavior of nss_files where if two separate lines are specified for
the same group in /etc/groups, getgrnam()/getgrgid() will display both.
Clients are already expected to handle this gracefully.

== No Premature Optimizations ==
The following is a list of places that might be eligible for
optimization, but were not overengineered for this initial contribution:
 * Any situation where a merge may occur will result in one malloc() of
   the same size as the input buffer.
 * Any situation where a merge does occur will result in a second
   malloc() to hold the list of pointers to member name strings.
 * The list of members is simply concatenated together and is not tested
   for uniqueness (which is identical to the behavior for nss_files,
   which will simply return identical values if they both exist on the
   line in the file. This could potentially be optimized to reduce space
   usage in the buffer, but it is both complex and computationally
   expensive to do so.

== Testing ==
I performed testing by running the getent utility against my newly-built
glibc and configuring /etc/nsswitch.conf with the following entry:
group: group:      files [SUCCESS=merge] sss

In /etc/group I included the line:
wheel:x:10:sgallagh

I then configured my local SSSD using the id_provider=local to respond
with:
wheel:*:10:localuser,localuser2

I then ran `getent group wheel` against the newly-built glibc in
multiple situations and received the expected output as described
above:
 * When SSSD was running.
 * When SSSD was configured in nsswitch.conf but the daemon was not
   running.
 * When SSSD was configured in nsswitch.conf but nss_sss.so.2 was not
   installed on the system.
 * When the order of 'sss' and 'files' was reversed.
 * All of the above with the [SUCCESS=merge] removed (to ensure no
   regressions).
 * All of the above with `getent group 10`.
 * All of the above with `getent group` with and without
   `enumerate=true` set in SSSD.
 * All of the above with and without nscd enabled on the system.

== NEWS ==

* A new NSS action is added to facilitate large distribution system
  administration.  The action, MERGE, allows remote user stores like
  LDAP to be merged into local user stores like /etc/groups in order
  to provide easy to use, updated, and managed sets of merged
  credentials.  The new action can be used by configuring it in
  /etc/nsswitch.conf:
  group: files [SUCCESS=merge] nis
  Implemented by Stephen Gallagher (Red Hat).

== ChangeLog ==

2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>

	[BZ #19072]
	* grp/Makefile (headers): Add grp-merge.h
	(routines): Add grp-merge.
	* grp/getgrgid_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/getgrname_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/grp-merge.c: New file.
	* grp/grp-merge.h: New file.
	* manual/nss.texi (Actions in the NSS configuration): Describe
	return, continue, and merge.
	* nscd/Makefile: Add vpath to find grp-merge.c
	(nscd-modules): Add grp-merge.
	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
	[!MERGE_FN]: Define __merge_einval.
	(CHECK_MERGE): Define.
	(REENTRANT_NAME): Process merge if do_merge is true.
	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
	(__nss_getent_r): Likewise.
	* nss/nsswitch.c (nss_parse_service_list): Likewise.
	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

Resolves BZ #19072
---
 grp/Makefile      |   5 +-
 grp/getgrgid_r.c  |   4 ++
 grp/getgrnam_r.c  |   4 ++
 grp/grp-merge.c   | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 grp/grp-merge.h   |  35 +++++++++++
 manual/nss.texi   |  46 +++++++++++++-
 nscd/Makefile     |   5 +-
 nscd/getgrgid_r.c |   4 ++
 nscd/getgrnam_r.c |   4 ++
 nss/getXXbyYY_r.c | 105 +++++++++++++++++++++++++++++++-
 nss/getnssent_r.c |  36 ++++++++++-
 nss/nsswitch.c    |   3 +
 nss/nsswitch.h    |   3 +-
 13 files changed, 421 insertions(+), 10 deletions(-)
 create mode 100644 grp/grp-merge.c
 create mode 100644 grp/grp-merge.h

diff --git a/grp/Makefile b/grp/Makefile
index ed8cc2b0564f0e3842cd78f24a4e0788d659bbc4..52af992365268aae8cf8a80cd7216160b1431e84 100644
--- a/grp/Makefile
+++ b/grp/Makefile
@@ -20,15 +20,16 @@
 #
 subdir	:= grp
 
 include ../Makeconfig
 
-headers := grp.h
+headers := grp.h grp-merge.h
 
 routines := fgetgrent initgroups setgroups \
 	    getgrent getgrgid getgrnam putgrent \
-	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r
+	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r \
+	    grp-merge
 
 tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
index 05d4d772d3ef0bfae8f9375387c41310885ce41a..5bd32b9881e54a46262df668542d5f925c44dd93 100644
--- a/grp/getgrgid_r.c
+++ b/grp/getgrgid_r.c
@@ -16,14 +16,18 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
+
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
 
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c
index 0061cb2f7e0bd311d19775e49eb3fdd8a93447f1..c5535f4057ddfc40965f27789c34345045c8bf3b 100644
--- a/grp/getgrnam_r.c
+++ b/grp/getgrnam_r.c
@@ -16,13 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/grp-merge.c b/grp/grp-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..ccc3fe1a1c2c18eddc9a53a7a0fffdad43d9c514
--- /dev/null
+++ b/grp/grp-merge.c
@@ -0,0 +1,177 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include "grp-merge.h"
+
+#define BUFCHECK(size)		\
+  do {				\
+    if (c + size > buflen) {	\
+        free (members);		\
+        return ERANGE;		\
+    }				\
+  } while(0)
+
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+{
+  size_t i;
+  size_t c = 0;
+  size_t len;
+  size_t memcount;
+  char **members = NULL;
+
+  /* Copy the GID.  */
+  destgrp->gr_gid = srcgrp.gr_gid;
+
+  /* Copy the name.  */
+  len = sizeof (char) * (strlen (srcgrp.gr_name) + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_name, len);
+  destgrp->gr_name = &destbuf[c];
+  c += len;
+
+  /* Copy the password.  */
+  len = sizeof (char) * (strlen (srcgrp.gr_passwd) + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+  destgrp->gr_passwd = &destbuf[c];
+  c += len;
+
+  /* Count all of the members.  */
+  for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+    ;
+
+  /* Allocate a temporary holding area for the pointers to the member
+     contents, including space for a NULL-terminator.  */
+  members = malloc (sizeof (char *) * (memcount + 1));
+  if (members == NULL)
+      return ENOMEM;
+
+  /* Copy all of the group members to destbuf and add a pointer to each of
+     them into the 'members' array.  */
+  for (i = 0; srcgrp.gr_mem[i]; i++)
+    {
+      len = sizeof (char) * (strlen (srcgrp.gr_mem[i]) + 1);
+      BUFCHECK (len);
+      memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+      members[i] = &destbuf[c];
+      c += len;
+    }
+  members[i] = NULL;
+
+  /* Copy the pointers from the members array into the buffer and assign them
+     to the gr_mem member of destgrp.  */
+  destgrp->gr_mem = (char **) &destbuf[c];
+  len = sizeof (char *) * (memcount + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], members, len);
+  c += len;
+  free (members);
+  members = NULL;
+
+  /* Save the count of members at the end.  */
+  BUFCHECK (sizeof (size_t));
+  memcpy (&destbuf[c], &memcount, sizeof (size_t));
+  c += sizeof (size_t);
+
+  if (endptr)
+      *endptr = destbuf + c;
+  return 0;
+}
+
+/* Check that the name, GID and passwd fields match, then
+   copy in the gr_mem array.  */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+  size_t c, i, len;
+  size_t savedmemcount;
+  size_t memcount;
+  size_t membersize;
+  char **members = NULL;
+
+  /* We only support merging members of groups with identical names and
+     GID values. If we hit this case, we need to overwrite the current
+     buffer with the saved one (which is functionally equivalent to
+     treating the new lookup as NSS_STATUS NOTFOUND.  */
+  if (mergegrp->gr_gid != savedgrp->gr_gid
+      || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+      return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+
+  /* Get the count of group members from the last sizeof (size_t) bytes in the
+     mergegrp buffer.  */
+  savedmemcount = (size_t) *(savedend - sizeof (size_t));
+
+  /* Get the count of new members to add.  */
+  for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+    ;
+
+  /* Create a temporary array to hold the pointers to the member values from
+     both the saved and merge groups.  */
+  membersize = savedmemcount + memcount + 1;
+  members = malloc (sizeof (char *) * membersize);
+  if (members == NULL)
+      return ENOMEM;
+
+  /* Copy in the existing member pointers from the saved group
+     Note: this is not NULL-terminated yet.  */
+  memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount);
+
+  /* Back up into the savedbuf until we get back to the NULL-terminator of the
+     group member list. (This means walking back savedmemcount + 1 (char *) pointers
+     and the member count value.
+     The value of c is going to be the used length of the buffer backed up by
+     the member count and further backed up by the size of the pointers.  */
+  c = savedend - savedbuf
+      - sizeof (size_t)
+      - sizeof (char *) * (savedmemcount + 1);
+
+  /* Add all the new group members, overwriting the old NULL-terminator while
+     adding the new pointers to the temporary array.  */
+  for (i = 0; mergegrp->gr_mem[i]; i++)
+    {
+      len = sizeof (char) * (strlen (mergegrp->gr_mem[i]) + 1);
+      BUFCHECK (len);
+      memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+      members[savedmemcount + i] = &savedbuf[c];
+      c += len;
+    }
+  /* Add the NULL-terminator.  */
+  members[savedmemcount + memcount] = NULL;
+
+  /* Copy the member array back into the buffer after the member list and free
+     the member array.  */
+  savedgrp->gr_mem = (char **) &savedbuf[c];
+  len = sizeof (char *) * membersize;
+  BUFCHECK (len);
+  memcpy (&savedbuf[c], members, len);
+  c += len;
+
+  free (members);
+  members = NULL;
+
+  /* Finally, copy the results back into mergebuf, since that's the buffer
+     that we were provided by the caller.  */
+  return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
diff --git a/grp/grp-merge.h b/grp/grp-merge.h
new file mode 100644
index 0000000000000000000000000000000000000000..59013487d0d907c76521ab504e265077937bfb5e
--- /dev/null
+++ b/grp/grp-merge.h
@@ -0,0 +1,35 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
+#include <grp.h>
+
+/* Duplicate a grp struct (and its members). When no longer needed, the
+   calling function must free(newbuf). */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr);
+
+/* Merge the member lists of two grp structs together. */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf);
+
+#endif /* _GRP_MERGE_H */
diff --git a/manual/nss.texi b/manual/nss.texi
index 66dcceffe01f225f078e88dd006bb90e80c85723..95e3544bcd97995720e7154ec43df4090154bf4c 100644
--- a/manual/nss.texi
+++ b/manual/nss.texi
@@ -178,11 +178,11 @@ where
 @var{action} @result{} return | continue
 @end smallexample
 
 The case of the keywords is insignificant.  The @var{status}
 values are the results of a call to a lookup function of a specific
-service.  They mean
+service.  They mean:
 
 @ftable @samp
 @item success
 No error occurred and the wanted entry is returned.  The default action
 for this is @code{return}.
@@ -202,10 +202,54 @@ The service is temporarily unavailable.  This could mean a file is
 locked or a server currently cannot accept more connections.  The
 default action is @code{continue}.
 @end ftable
 
 @noindent
+The @var{action} values mean:
+
+@ftable @samp
+@item return
+
+If the status matches, stop the lookup process at this service
+specification.  If an entry is available, provide it to the application.
+If an error occurred, report it to the application.  In case of a prior
+@samp{merge} action, the data is combined with previous lookup results,
+as explained below.
+
+@item continue
+
+If the status matches, proceed with the lookup process at the next
+entry, discarding the result of the current lookup (and any merged
+data).  An exception is the @samp{initgroups} database and the
+@samp{success} status, where @samp{continue} acts like @code{merge}
+below.
+
+@item merge
+
+Proceed with the lookup process, retaining the current lookup result.
+This action is useful only with the @samp{success} status.  If a
+subsequent service lookup succeeds and has a matching @samp{return}
+specification, the results are merged, the lookup process ends, and the
+merged results are returned to the application.  If the following service
+has a matching @samp{merge} action, the lookup process continues,
+retaining the combined data from this and any previous lookups.
+
+After a @code{merge} action, errors from subsequent lookups are ignored,
+and the data gathered so far will be returned.
+
+The @samp{merge} only applies to the @samp{success} status.  It is
+currently implemented for the @samp{group} database and its group
+members field, @samp{gr_mem}.  If specified for other databases, it
+causes the lookup to fail (if the @var{status} matches).
+
+When processing @samp{merge} for @samp{group} membership, the group GID
+and name must be identical for both entries. If only one or the other is
+a match, the behavior is undefined.
+
+@end ftable
+
+@noindent
 If we have a line like
 
 @smallexample
 ethers: nisplus [NOTFOUND=return] db files
 @end smallexample
diff --git a/nscd/Makefile b/nscd/Makefile
index e1a1aa92fc699aa132f7192da49f698a078e5910..3e6895573ae33221c728617f4c95bb3e8c5d5c47 100644
--- a/nscd/Makefile
+++ b/nscd/Makefile
@@ -29,16 +29,19 @@ aux	:= nscd_helper
 endif
 
 # To find xmalloc.c
 vpath %.c ../locale/programs
 
+# To find grp-merge.c
+vpath %.c ../grp
+
 nscd-modules := nscd connections pwdcache getpwnam_r getpwuid_r grpcache \
 		getgrnam_r getgrgid_r hstcache gethstbyad_r gethstbynm3_r \
 		getsrvbynm_r getsrvbypt_r servicescache \
 		dbg_log nscd_conf nscd_stat cache mem nscd_setup_thread \
 		xmalloc xstrdup aicache initgrcache gai res_hconf \
-		netgroupcache
+		netgroupcache grp-merge
 
 ifeq ($(build-nscd)$(have-thread-library),yesyes)
 
 others += nscd
 others-pie += nscd
diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c
index fe5bda424169d56f642f125ef1f2df77a84de221..25de4a3b0b74841c44844a0541cf4d2365b22515 100644
--- a/nscd/getgrgid_r.c
+++ b/nscd/getgrgid_r.c
@@ -15,17 +15,21 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c
index 5ec56877f5798ca34c2e0074d5093cc22b6d58dc..386d66c5832ffee68e95195f6e34b723f41d0984 100644
--- a/nscd/getgrnam_r.c
+++ b/nscd/getgrnam_r.c
@@ -15,16 +15,20 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 198f8cfebd51be9c738f03f950f093b0855ab3cb..5f49ae8b828fce6dc30c7ccc9606319bffc52d5b 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -129,10 +129,52 @@
 # define AF_VAL af
 #else
 # define AF_VAL AF_INET
 #endif
 
+
+/* Set defaults for merge functions that haven't been defined.  */
+#ifndef DEEPCOPY_FN
+static inline int
+__copy_einval (LOOKUP_TYPE a,
+	      const size_t b,
+	      LOOKUP_TYPE *c,
+	      char *d,
+	      char **e)
+{
+  return EINVAL;
+}
+# define DEEPCOPY_FN __copy_einval
+#endif
+
+#ifndef MERGE_FN
+static inline int
+__merge_einval (LOOKUP_TYPE *a,
+	       char *b,
+	       char *c,
+	       size_t d,
+	       LOOKUP_TYPE *e,
+	       char *f)
+{
+  return EINVAL;
+}
+# define MERGE_FN __merge_einval
+#endif
+
+#define CHECK_MERGE(err, status)	\
+do {					\
+  if (err)				\
+    {					\
+      __set_errno (err);		\
+      if (err == ERANGE)		\
+          status = NSS_STATUS_TRYAGAIN;	\
+      else				\
+          status = NSS_STATUS_UNAVAIL;	\
+      break;				\
+    }					\
+} while(0)
+
 /* Type of the lookup function we need here.  */
 typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *,
 					    size_t, int * H_ERRNO_PARM
 					    EXTRA_PARAMS);
 
@@ -150,17 +192,20 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 {
   static bool startp_initialized;
   static service_user *startp;
   static lookup_function start_fct;
   service_user *nip;
+  int do_merge = 0;
+  LOOKUP_TYPE mergegrp;
+  char *mergebuf = NULL;
+  char *endptr = NULL;
   union
   {
     lookup_function l;
     void *ptr;
   } fct;
-
-  int no_more;
+  int no_more, err;
   enum nss_status status = NSS_STATUS_UNAVAIL;
 #ifdef USE_NSCD
   int nscd_status;
 #endif
 #ifdef NEED_H_ERRNO
@@ -276,13 +321,69 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && *h_errnop == NETDB_INTERNAL
 #endif
 	  && errno == ERANGE)
 	break;
 
+      if (do_merge)
+	{
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+		/* The previous loop saved a buffer for merging.
+		   Perform the merge now.  */
+		err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
+				buffer);
+		CHECK_MERGE (err,status);
+		do_merge = 0;
+	    }
+	  else
+	    {
+	      /* If the result wasn't SUCCESS, copy the saved buffer back
+	         into the result buffer and set the status back to
+	         NSS_STATUS_SUCCESS to match the previous pass through the loop.
+	          * If the next action is CONTINUE, it will overwrite the value
+	            currently in the buffer and return the new value.
+	          * If the next action is RETURN, we'll return the previously-
+	            acquired values.
+	          * If the next action is MERGE, then it will be added to the buffer
+	            saved from the previous source.  */
+	      err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL);
+	      CHECK_MERGE (err, status);
+	      status = NSS_STATUS_SUCCESS;
+	    }
+	}
+
+      /* If we were are configured to merge this value with the next one,
+         save the current value of the group struct.  */
+      if (nss_next_action (nip, status) == NSS_ACTION_MERGE
+	  && status == NSS_STATUS_SUCCESS)
+	{
+	  /* Copy the current values into a buffer to be merged with the next
+	     set of retrieved values.  */
+	  if (!mergebuf)
+	    {
+	      /* Only allocate once and reuse it for as many merges as we need
+	         to perform.  */
+	      mergebuf = malloc (buflen);
+	      if (!mergebuf)
+		{
+		  __set_errno (ENOMEM);
+		  status = NSS_STATUS_UNAVAIL;
+		  break;
+		}
+	    }
+
+	  err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr);
+	  CHECK_MERGE (err, status);
+	  do_merge = 1;
+	}
+
       no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING,
 			     REENTRANT2_NAME_STRING, &fct.ptr, status, 0);
     }
+  free(mergebuf);
+  mergebuf = NULL;
 
 #ifdef HANDLE_DIGITS_DOTS
 done:
 #endif
   *result = status == NSS_STATUS_SUCCESS ? resbuf : NULL;
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index f5b903671ca53ccad108eeb4e49ea40a45fa5cdf..1d421ea19868fb0e9b00ec5b3b86e5958c681ab0 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -77,11 +77,26 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       if (stayopen_tmp)
 	status = DL_CALL_FCT (fct.f, (*stayopen_tmp));
       else
 	status = DL_CALL_FCT (fct.f, (0));
 
-      no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	{
+	  /* This is a special-case. When [SUCCESS=merge] is in play,
+	     _nss_next2() will skip to the next database.  Due to the
+	     implementation of that function, we can't know whether we're
+	     in an enumeration or an individual lookup, which behaves
+	     differently with regards to merging.  We'll treat SUCCESS as
+	     an indication to start the enumeration at this database.
+	   */
+	  no_more = 1;
+	}
+      else
+	{
+	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+	}
+
       if (is_last_nip)
 	*last_nip = *nip;
     }
 
   if (stayopen_tmp)
@@ -173,12 +188,27 @@ __nss_getent_r (const char *getent_func_name,
 	  && errno == ERANGE)
 	break;
 
       do
 	{
-	  no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
-				 status, 0);
+	  if (status == NSS_STATUS_SUCCESS
+	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	    {
+	      /* This is a special-case. When [SUCCESS=merge] is in play,
+	         _nss_next2() will skip to the next database.  Due to the
+	         implementation of that function, we can't know whether we're
+	         in an enumeration or an individual lookup, which behaves
+	         differently with regards to merging.  We'll treat SUCCESS as
+	         an indication to return the results here.
+	       */
+	      no_more = 1;
+	    }
+	  else
+	    {
+	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
+				     status, 0);
+	    }
 
 	  if (is_last_nip)
 	    *last_nip = *nip;
 
 	  if (! no_more)
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index faf9d1a0d5680aa79e88b2dfeea18da371c336fb..f8f60ba05aad9a571f928a3ea7d3b14f908ccb2d 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -710,10 +710,13 @@ nss_parse_service_list (const char *line)
 	      if (line - name == 6 && __strncasecmp (name, "RETURN", 6) == 0)
 		action = NSS_ACTION_RETURN;
 	      else if (line - name == 8
 		       && __strncasecmp (name, "CONTINUE", 8) == 0)
 		action = NSS_ACTION_CONTINUE;
+	      else if (line - name == 5
+		       && __strncasecmp (name, "MERGE", 5) == 0)
+		action = NSS_ACTION_MERGE;
 	      else
 		goto finish;
 
 	      if (not)
 		{
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index a5318fa82be43c8314807ad76de231e572e91c06..5bc2de3b1d82978102ac7a129c0ba5b7eb3cfd25 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -30,11 +30,12 @@
 
 /* Actions performed after lookup finished.  */
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;
 
 
 typedef struct service_library
 {
-- 
2.5.0

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-01-04 17:40         ` Stephen Gallagher
@ 2016-01-06 22:41           ` Andreas Schwab
  2016-01-08  0:46             ` Stephen Gallagher
  0 siblings, 1 reply; 33+ messages in thread
From: Andreas Schwab @ 2016-01-06 22:41 UTC (permalink / raw)
  To: Stephen Gallagher; +Cc: libc-alpha

Stephen Gallagher <sgallagh@redhat.com> writes:

> diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
> index 05d4d772d3ef0bfae8f9375387c41310885ce41a..5bd32b9881e54a46262df668542d5f925c44dd93 100644
> --- a/grp/getgrgid_r.c
> +++ b/grp/getgrgid_r.c
> @@ -16,14 +16,18 @@
>     License along with the GNU C Library; if not, see
>     <http://www.gnu.org/licenses/>.  */
>  
>  #include <grp.h>
>  
> +#include "grp-merge.h"
> +
>  

Spurious empty line.

> +#define BUFCHECK(size)		\
> +  do {				\
> +    if (c + size > buflen) {	\
> +        free (members);		\
> +        return ERANGE;		\
> +    }				\
> +  } while(0)

Wrong brace style.

> +
> +  /* Copy all of the group members to destbuf and add a pointer to each of
> +     them into the 'members' array.  */
> +  for (i = 0; srcgrp.gr_mem[i]; i++)
> +    {
> +      len = sizeof (char) * (strlen (srcgrp.gr_mem[i]) + 1);

sizeof (char) is 1.  Drop it.

> +  /* Allocate a temporary holding area for the pointers to the member
> +     contents, including space for a NULL-terminator.  */
> +  members = malloc (sizeof (char *) * (memcount + 1));
> +  if (members == NULL)
> +      return ENOMEM;

Wrong indent.

> +  if (endptr)
> +      *endptr = destbuf + c;

Likewise.  (And several more.)

> +      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
> +	{
> +	  /* This is a special-case. When [SUCCESS=merge] is in play,
> +	     _nss_next2() will skip to the next database.  Due to the
> +	     implementation of that function, we can't know whether we're
> +	     in an enumeration or an individual lookup, which behaves
> +	     differently with regards to merging.  We'll treat SUCCESS as
> +	     an indication to start the enumeration at this database.
> +	   */

No NL before */.

> +	  if (status == NSS_STATUS_SUCCESS
> +	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
> +	    {
> +	      /* This is a special-case. When [SUCCESS=merge] is in play,
> +	         _nss_next2() will skip to the next database.  Due to the
> +	         implementation of that function, we can't know whether we're
> +	         in an enumeration or an individual lookup, which behaves
> +	         differently with regards to merging.  We'll treat SUCCESS as
> +	         an indication to return the results here.
> +	       */

Likewise.

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5
"And now for something completely different."

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH] nsswitch: Add group merging support
  2016-01-06 22:41           ` Andreas Schwab
@ 2016-01-08  0:46             ` Stephen Gallagher
  2016-01-13  1:31               ` Carlos O'Donell
  0 siblings, 1 reply; 33+ messages in thread
From: Stephen Gallagher @ 2016-01-08  0:46 UTC (permalink / raw)
  To: libc-alpha

https://sourceware.org/glibc/wiki/Proposals/GroupMerging

== Justification ==
It is common today for users to rely on centrally-managed user stores for
handling their user accounts. However, much software existing today does
not have an innate understanding of such accounts. Instead, they commonly
rely on membership in known groups for managing access-control (for
example the "wheel" group on Fedora and RHEL systems or the "adm" group
on Debian-derived systems). In the present incarnation of nsswitch, the
only way to have such groups managed by a remote user store such as
FreeIPA or Active Directory would be to manually remove the groups from
/etc/group on the clients so that nsswitch would then move past nss_files
and into the SSSD, nss-ldap or other remote user database.

== Solution ==
With this patch, a new action is introduced for nsswitch:
NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
between two database entries in the nsswitch.conf file. When a group is
located in the first of the two group entries, processing will continue
on to the next one. If the group is also found in the next entry (and the
group name and GID are an exact match), the member list of the second
entry will be added to the group object to be returned.

== Implementation ==
After each DL_LOOKUP_FN() returns, the next action is checked. If the
function returned NSS_STATUS_SUCCESS and the next action is
NSS_ACTION_MERGE, a copy of the result buffer is saved for the next pass
through the loop. If on this next pass through the loop the database
returns another instance of a group matching both the group name and GID,
the member list is added to the previous list and it is returned as a
single object. If the following database does not contain the same group,
then the original is copied back into the destination buffer.

This patch implements merge functionality only for the group database.
For other databases, there is a default implementation that will return
the EINVAL errno if a merge is requested. The merge functionality can be
implemented for other databases at a later time if such is needed. Each
database must provide a unique implementation of the deep-copy and merge
functions.

If [SUCCESS=merge] is present in nsswitch.conf for a glibc version that
does not support it, glibc will process results up until that operation,
at which time it will return results if it has found them or else will
simply return an error. In practical terms, this ends up behaving like
the remainder of the nsswitch.conf line does not exist.

== Iterators ==
This feature does not modify the iterator functionality from its current
behavior. If getgrnam() or getgrgid() is called, glibc will iterate
through all entries in the `group` line in nsswitch.conf and display the
list of members without attempting to merge them. This is consistent with
the behavior of nss_files where if two separate lines are specified for
the same group in /etc/groups, getgrnam()/getgrgid() will display both.
Clients are already expected to handle this gracefully.

== No Premature Optimizations ==
The following is a list of places that might be eligible for
optimization, but were not overengineered for this initial contribution:
 * Any situation where a merge may occur will result in one malloc() of
   the same size as the input buffer.
 * Any situation where a merge does occur will result in a second
   malloc() to hold the list of pointers to member name strings.
 * The list of members is simply concatenated together and is not tested
   for uniqueness (which is identical to the behavior for nss_files,
   which will simply return identical values if they both exist on the
   line in the file. This could potentially be optimized to reduce space
   usage in the buffer, but it is both complex and computationally
   expensive to do so.

== Testing ==
I performed testing by running the getent utility against my newly-built
glibc and configuring /etc/nsswitch.conf with the following entry:
group: group:      files [SUCCESS=merge] sss

In /etc/group I included the line:
wheel:x:10:sgallagh

I then configured my local SSSD using the id_provider=local to respond
with:
wheel:*:10:localuser,localuser2

I then ran `getent group wheel` against the newly-built glibc in
multiple situations and received the expected output as described
above:
 * When SSSD was running.
 * When SSSD was configured in nsswitch.conf but the daemon was not
   running.
 * When SSSD was configured in nsswitch.conf but nss_sss.so.2 was not
   installed on the system.
 * When the order of 'sss' and 'files' was reversed.
 * All of the above with the [SUCCESS=merge] removed (to ensure no
   regressions).
 * All of the above with `getent group 10`.
 * All of the above with `getent group` with and without
   `enumerate=true` set in SSSD.
 * All of the above with and without nscd enabled on the system.

== NEWS ==

* A new NSS action is added to facilitate large distribution system
  administration.  The action, MERGE, allows remote user stores like
  LDAP to be merged into local user stores like /etc/groups in order
  to provide easy to use, updated, and managed sets of merged
  credentials.  The new action can be used by configuring it in
  /etc/nsswitch.conf:
  group: files [SUCCESS=merge] nis
  Implemented by Stephen Gallagher (Red Hat).

== ChangeLog ==

2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>

	[BZ #19072]
	* grp/Makefile (headers): Add grp-merge.h
	(routines): Add grp-merge.
	* grp/getgrgid_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/getgrname_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/grp-merge.c: New file.
	* grp/grp-merge.h: New file.
	* manual/nss.texi (Actions in the NSS configuration): Describe
	return, continue, and merge.
	* nscd/Makefile: Add vpath to find grp-merge.c
	(nscd-modules): Add grp-merge.
	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
	[!MERGE_FN]: Define __merge_einval.
	(CHECK_MERGE): Define.
	(REENTRANT_NAME): Process merge if do_merge is true.
	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
	(__nss_getent_r): Likewise.
	* nss/nsswitch.c (nss_parse_service_list): Likewise.
	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

Resolves BZ #19072
---
 grp/Makefile      |   5 +-
 grp/getgrgid_r.c  |   3 +
 grp/getgrnam_r.c  |   4 ++
 grp/grp-merge.c   | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 grp/grp-merge.h   |  35 +++++++++++
 manual/nss.texi   |  46 +++++++++++++-
 nscd/Makefile     |   5 +-
 nscd/getgrgid_r.c |   4 ++
 nscd/getgrnam_r.c |   4 ++
 nss/getXXbyYY_r.c | 105 +++++++++++++++++++++++++++++++-
 nss/getnssent_r.c |  34 ++++++++++-
 nss/nsswitch.c    |   3 +
 nss/nsswitch.h    |   3 +-
 13 files changed, 419 insertions(+), 10 deletions(-)
 create mode 100644 grp/grp-merge.c
 create mode 100644 grp/grp-merge.h

diff --git a/grp/Makefile b/grp/Makefile
index ed8cc2b0564f0e3842cd78f24a4e0788d659bbc4..52af992365268aae8cf8a80cd7216160b1431e84 100644
--- a/grp/Makefile
+++ b/grp/Makefile
@@ -20,15 +20,16 @@
 #
 subdir	:= grp
 
 include ../Makeconfig
 
-headers := grp.h
+headers := grp.h grp-merge.h
 
 routines := fgetgrent initgroups setgroups \
 	    getgrent getgrgid getgrnam putgrent \
-	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r
+	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r \
+	    grp-merge
 
 tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
index 05d4d772d3ef0bfae8f9375387c41310885ce41a..447fa633807deec8f26d654ebeb6386a150d3a37 100644
--- a/grp/getgrgid_r.c
+++ b/grp/getgrgid_r.c
@@ -16,14 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
 
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c
index 0061cb2f7e0bd311d19775e49eb3fdd8a93447f1..c5535f4057ddfc40965f27789c34345045c8bf3b 100644
--- a/grp/getgrnam_r.c
+++ b/grp/getgrnam_r.c
@@ -16,13 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/grp-merge.c b/grp/grp-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..ca959dbfe403c89d6f3184f2b361b0c6488c9182
--- /dev/null
+++ b/grp/grp-merge.c
@@ -0,0 +1,178 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include "grp-merge.h"
+
+#define BUFCHECK(size)		\
+  do {						\
+    if (c + size > buflen)	\
+    {						\
+        free (members);		\
+        return ERANGE;		\
+    }						\
+  } while(0)
+
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+{
+  size_t i;
+  size_t c = 0;
+  size_t len;
+  size_t memcount;
+  char **members = NULL;
+
+  /* Copy the GID.  */
+  destgrp->gr_gid = srcgrp.gr_gid;
+
+  /* Copy the name.  */
+  len = strlen (srcgrp.gr_name) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_name, len);
+  destgrp->gr_name = &destbuf[c];
+  c += len;
+
+  /* Copy the password.  */
+  len = strlen (srcgrp.gr_passwd) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+  destgrp->gr_passwd = &destbuf[c];
+  c += len;
+
+  /* Count all of the members.  */
+  for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+    ;
+
+  /* Allocate a temporary holding area for the pointers to the member
+     contents, including space for a NULL-terminator.  */
+  members = malloc (sizeof (char *) * (memcount + 1));
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy all of the group members to destbuf and add a pointer to each of
+     them into the 'members' array.  */
+  for (i = 0; srcgrp.gr_mem[i]; i++)
+    {
+      len = strlen (srcgrp.gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+      members[i] = &destbuf[c];
+      c += len;
+    }
+  members[i] = NULL;
+
+  /* Copy the pointers from the members array into the buffer and assign them
+     to the gr_mem member of destgrp.  */
+  destgrp->gr_mem = (char **) &destbuf[c];
+  len = sizeof (char *) * (memcount + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], members, len);
+  c += len;
+  free (members);
+  members = NULL;
+
+  /* Save the count of members at the end.  */
+  BUFCHECK (sizeof (size_t));
+  memcpy (&destbuf[c], &memcount, sizeof (size_t));
+  c += sizeof (size_t);
+
+  if (endptr)
+    *endptr = destbuf + c;
+  return 0;
+}
+
+/* Check that the name, GID and passwd fields match, then
+   copy in the gr_mem array.  */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+  size_t c, i, len;
+  size_t savedmemcount;
+  size_t memcount;
+  size_t membersize;
+  char **members = NULL;
+
+  /* We only support merging members of groups with identical names and
+     GID values. If we hit this case, we need to overwrite the current
+     buffer with the saved one (which is functionally equivalent to
+     treating the new lookup as NSS_STATUS NOTFOUND.  */
+  if (mergegrp->gr_gid != savedgrp->gr_gid
+      || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+    return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+
+  /* Get the count of group members from the last sizeof (size_t) bytes in the
+     mergegrp buffer.  */
+  savedmemcount = (size_t) *(savedend - sizeof (size_t));
+
+  /* Get the count of new members to add.  */
+  for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+    ;
+
+  /* Create a temporary array to hold the pointers to the member values from
+     both the saved and merge groups.  */
+  membersize = savedmemcount + memcount + 1;
+  members = malloc (sizeof (char *) * membersize);
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy in the existing member pointers from the saved group
+     Note: this is not NULL-terminated yet.  */
+  memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount);
+
+  /* Back up into the savedbuf until we get back to the NULL-terminator of the
+     group member list. (This means walking back savedmemcount + 1 (char *) pointers
+     and the member count value.
+     The value of c is going to be the used length of the buffer backed up by
+     the member count and further backed up by the size of the pointers.  */
+  c = savedend - savedbuf
+      - sizeof (size_t)
+      - sizeof (char *) * (savedmemcount + 1);
+
+  /* Add all the new group members, overwriting the old NULL-terminator while
+     adding the new pointers to the temporary array.  */
+  for (i = 0; mergegrp->gr_mem[i]; i++)
+    {
+      len = strlen (mergegrp->gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+      members[savedmemcount + i] = &savedbuf[c];
+      c += len;
+    }
+  /* Add the NULL-terminator.  */
+  members[savedmemcount + memcount] = NULL;
+
+  /* Copy the member array back into the buffer after the member list and free
+     the member array.  */
+  savedgrp->gr_mem = (char **) &savedbuf[c];
+  len = sizeof (char *) * membersize;
+  BUFCHECK (len);
+  memcpy (&savedbuf[c], members, len);
+  c += len;
+
+  free (members);
+  members = NULL;
+
+  /* Finally, copy the results back into mergebuf, since that's the buffer
+     that we were provided by the caller.  */
+  return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
diff --git a/grp/grp-merge.h b/grp/grp-merge.h
new file mode 100644
index 0000000000000000000000000000000000000000..59013487d0d907c76521ab504e265077937bfb5e
--- /dev/null
+++ b/grp/grp-merge.h
@@ -0,0 +1,35 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
+#include <grp.h>
+
+/* Duplicate a grp struct (and its members). When no longer needed, the
+   calling function must free(newbuf). */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr);
+
+/* Merge the member lists of two grp structs together. */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf);
+
+#endif /* _GRP_MERGE_H */
diff --git a/manual/nss.texi b/manual/nss.texi
index 66dcceffe01f225f078e88dd006bb90e80c85723..95e3544bcd97995720e7154ec43df4090154bf4c 100644
--- a/manual/nss.texi
+++ b/manual/nss.texi
@@ -178,11 +178,11 @@ where
 @var{action} @result{} return | continue
 @end smallexample
 
 The case of the keywords is insignificant.  The @var{status}
 values are the results of a call to a lookup function of a specific
-service.  They mean
+service.  They mean:
 
 @ftable @samp
 @item success
 No error occurred and the wanted entry is returned.  The default action
 for this is @code{return}.
@@ -202,10 +202,54 @@ The service is temporarily unavailable.  This could mean a file is
 locked or a server currently cannot accept more connections.  The
 default action is @code{continue}.
 @end ftable
 
 @noindent
+The @var{action} values mean:
+
+@ftable @samp
+@item return
+
+If the status matches, stop the lookup process at this service
+specification.  If an entry is available, provide it to the application.
+If an error occurred, report it to the application.  In case of a prior
+@samp{merge} action, the data is combined with previous lookup results,
+as explained below.
+
+@item continue
+
+If the status matches, proceed with the lookup process at the next
+entry, discarding the result of the current lookup (and any merged
+data).  An exception is the @samp{initgroups} database and the
+@samp{success} status, where @samp{continue} acts like @code{merge}
+below.
+
+@item merge
+
+Proceed with the lookup process, retaining the current lookup result.
+This action is useful only with the @samp{success} status.  If a
+subsequent service lookup succeeds and has a matching @samp{return}
+specification, the results are merged, the lookup process ends, and the
+merged results are returned to the application.  If the following service
+has a matching @samp{merge} action, the lookup process continues,
+retaining the combined data from this and any previous lookups.
+
+After a @code{merge} action, errors from subsequent lookups are ignored,
+and the data gathered so far will be returned.
+
+The @samp{merge} only applies to the @samp{success} status.  It is
+currently implemented for the @samp{group} database and its group
+members field, @samp{gr_mem}.  If specified for other databases, it
+causes the lookup to fail (if the @var{status} matches).
+
+When processing @samp{merge} for @samp{group} membership, the group GID
+and name must be identical for both entries. If only one or the other is
+a match, the behavior is undefined.
+
+@end ftable
+
+@noindent
 If we have a line like
 
 @smallexample
 ethers: nisplus [NOTFOUND=return] db files
 @end smallexample
diff --git a/nscd/Makefile b/nscd/Makefile
index e1a1aa92fc699aa132f7192da49f698a078e5910..3e6895573ae33221c728617f4c95bb3e8c5d5c47 100644
--- a/nscd/Makefile
+++ b/nscd/Makefile
@@ -29,16 +29,19 @@ aux	:= nscd_helper
 endif
 
 # To find xmalloc.c
 vpath %.c ../locale/programs
 
+# To find grp-merge.c
+vpath %.c ../grp
+
 nscd-modules := nscd connections pwdcache getpwnam_r getpwuid_r grpcache \
 		getgrnam_r getgrgid_r hstcache gethstbyad_r gethstbynm3_r \
 		getsrvbynm_r getsrvbypt_r servicescache \
 		dbg_log nscd_conf nscd_stat cache mem nscd_setup_thread \
 		xmalloc xstrdup aicache initgrcache gai res_hconf \
-		netgroupcache
+		netgroupcache grp-merge
 
 ifeq ($(build-nscd)$(have-thread-library),yesyes)
 
 others += nscd
 others-pie += nscd
diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c
index fe5bda424169d56f642f125ef1f2df77a84de221..25de4a3b0b74841c44844a0541cf4d2365b22515 100644
--- a/nscd/getgrgid_r.c
+++ b/nscd/getgrgid_r.c
@@ -15,17 +15,21 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c
index 5ec56877f5798ca34c2e0074d5093cc22b6d58dc..386d66c5832ffee68e95195f6e34b723f41d0984 100644
--- a/nscd/getgrnam_r.c
+++ b/nscd/getgrnam_r.c
@@ -15,16 +15,20 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 198f8cfebd51be9c738f03f950f093b0855ab3cb..5f49ae8b828fce6dc30c7ccc9606319bffc52d5b 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -129,10 +129,52 @@
 # define AF_VAL af
 #else
 # define AF_VAL AF_INET
 #endif
 
+
+/* Set defaults for merge functions that haven't been defined.  */
+#ifndef DEEPCOPY_FN
+static inline int
+__copy_einval (LOOKUP_TYPE a,
+	      const size_t b,
+	      LOOKUP_TYPE *c,
+	      char *d,
+	      char **e)
+{
+  return EINVAL;
+}
+# define DEEPCOPY_FN __copy_einval
+#endif
+
+#ifndef MERGE_FN
+static inline int
+__merge_einval (LOOKUP_TYPE *a,
+	       char *b,
+	       char *c,
+	       size_t d,
+	       LOOKUP_TYPE *e,
+	       char *f)
+{
+  return EINVAL;
+}
+# define MERGE_FN __merge_einval
+#endif
+
+#define CHECK_MERGE(err, status)	\
+do {					\
+  if (err)				\
+    {					\
+      __set_errno (err);		\
+      if (err == ERANGE)		\
+          status = NSS_STATUS_TRYAGAIN;	\
+      else				\
+          status = NSS_STATUS_UNAVAIL;	\
+      break;				\
+    }					\
+} while(0)
+
 /* Type of the lookup function we need here.  */
 typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *,
 					    size_t, int * H_ERRNO_PARM
 					    EXTRA_PARAMS);
 
@@ -150,17 +192,20 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 {
   static bool startp_initialized;
   static service_user *startp;
   static lookup_function start_fct;
   service_user *nip;
+  int do_merge = 0;
+  LOOKUP_TYPE mergegrp;
+  char *mergebuf = NULL;
+  char *endptr = NULL;
   union
   {
     lookup_function l;
     void *ptr;
   } fct;
-
-  int no_more;
+  int no_more, err;
   enum nss_status status = NSS_STATUS_UNAVAIL;
 #ifdef USE_NSCD
   int nscd_status;
 #endif
 #ifdef NEED_H_ERRNO
@@ -276,13 +321,69 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && *h_errnop == NETDB_INTERNAL
 #endif
 	  && errno == ERANGE)
 	break;
 
+      if (do_merge)
+	{
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+		/* The previous loop saved a buffer for merging.
+		   Perform the merge now.  */
+		err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
+				buffer);
+		CHECK_MERGE (err,status);
+		do_merge = 0;
+	    }
+	  else
+	    {
+	      /* If the result wasn't SUCCESS, copy the saved buffer back
+	         into the result buffer and set the status back to
+	         NSS_STATUS_SUCCESS to match the previous pass through the loop.
+	          * If the next action is CONTINUE, it will overwrite the value
+	            currently in the buffer and return the new value.
+	          * If the next action is RETURN, we'll return the previously-
+	            acquired values.
+	          * If the next action is MERGE, then it will be added to the buffer
+	            saved from the previous source.  */
+	      err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL);
+	      CHECK_MERGE (err, status);
+	      status = NSS_STATUS_SUCCESS;
+	    }
+	}
+
+      /* If we were are configured to merge this value with the next one,
+         save the current value of the group struct.  */
+      if (nss_next_action (nip, status) == NSS_ACTION_MERGE
+	  && status == NSS_STATUS_SUCCESS)
+	{
+	  /* Copy the current values into a buffer to be merged with the next
+	     set of retrieved values.  */
+	  if (!mergebuf)
+	    {
+	      /* Only allocate once and reuse it for as many merges as we need
+	         to perform.  */
+	      mergebuf = malloc (buflen);
+	      if (!mergebuf)
+		{
+		  __set_errno (ENOMEM);
+		  status = NSS_STATUS_UNAVAIL;
+		  break;
+		}
+	    }
+
+	  err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr);
+	  CHECK_MERGE (err, status);
+	  do_merge = 1;
+	}
+
       no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING,
 			     REENTRANT2_NAME_STRING, &fct.ptr, status, 0);
     }
+  free(mergebuf);
+  mergebuf = NULL;
 
 #ifdef HANDLE_DIGITS_DOTS
 done:
 #endif
   *result = status == NSS_STATUS_SUCCESS ? resbuf : NULL;
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index f5b903671ca53ccad108eeb4e49ea40a45fa5cdf..c0743436f661d4d83045a6353b49291a4c0f220b 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -77,11 +77,25 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       if (stayopen_tmp)
 	status = DL_CALL_FCT (fct.f, (*stayopen_tmp));
       else
 	status = DL_CALL_FCT (fct.f, (0));
 
-      no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	{
+	  /* This is a special-case. When [SUCCESS=merge] is in play,
+	     _nss_next2() will skip to the next database.  Due to the
+	     implementation of that function, we can't know whether we're
+	     in an enumeration or an individual lookup, which behaves
+	     differently with regards to merging.  We'll treat SUCCESS as
+	     an indication to start the enumeration at this database. */
+	  no_more = 1;
+	}
+      else
+	{
+	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+	}
+
       if (is_last_nip)
 	*last_nip = *nip;
     }
 
   if (stayopen_tmp)
@@ -173,12 +187,26 @@ __nss_getent_r (const char *getent_func_name,
 	  && errno == ERANGE)
 	break;
 
       do
 	{
-	  no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
-				 status, 0);
+	  if (status == NSS_STATUS_SUCCESS
+	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	    {
+	      /* This is a special-case. When [SUCCESS=merge] is in play,
+	         _nss_next2() will skip to the next database.  Due to the
+	         implementation of that function, we can't know whether we're
+	         in an enumeration or an individual lookup, which behaves
+	         differently with regards to merging.  We'll treat SUCCESS as
+	         an indication to return the results here. */
+	      no_more = 1;
+	    }
+	  else
+	    {
+	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
+				     status, 0);
+	    }
 
 	  if (is_last_nip)
 	    *last_nip = *nip;
 
 	  if (! no_more)
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index faf9d1a0d5680aa79e88b2dfeea18da371c336fb..f8f60ba05aad9a571f928a3ea7d3b14f908ccb2d 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -710,10 +710,13 @@ nss_parse_service_list (const char *line)
 	      if (line - name == 6 && __strncasecmp (name, "RETURN", 6) == 0)
 		action = NSS_ACTION_RETURN;
 	      else if (line - name == 8
 		       && __strncasecmp (name, "CONTINUE", 8) == 0)
 		action = NSS_ACTION_CONTINUE;
+	      else if (line - name == 5
+		       && __strncasecmp (name, "MERGE", 5) == 0)
+		action = NSS_ACTION_MERGE;
 	      else
 		goto finish;
 
 	      if (not)
 		{
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index a5318fa82be43c8314807ad76de231e572e91c06..5bc2de3b1d82978102ac7a129c0ba5b7eb3cfd25 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -30,11 +30,12 @@
 
 /* Actions performed after lookup finished.  */
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;
 
 
 typedef struct service_library
 {
-- 
2.6.4

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2015-12-29 21:58 ` Mike Frysinger
  2016-01-04 14:10   ` Stephen Gallagher
@ 2016-01-13  1:24   ` Carlos O'Donell
  2016-01-20 20:32   ` Carlos O'Donell
  2 siblings, 0 replies; 33+ messages in thread
From: Carlos O'Donell @ 2016-01-13  1:24 UTC (permalink / raw)
  To: Stephen Gallagher, libc-alpha

On 12/29/2015 04:58 PM, Mike Frysinger wrote:
>> I performed testing by running the getent utility against my newly-built
>> glibc and configuring /etc/nsswitch.conf with the following entry:
> 
> what will it take to get tests into glibc itself ?  ad-hoc testing is
> a great way for code to rot.

The way I'm going to test this is like so:

(a) Create two dummy nss modules: libnss_testmerge[12].so, which provide some
    data, and a small API to change data sets on the fly for testing.

(b) A master test using __nss_configure_lookup to override /etc/nsswitch.conf
    (This was news to me, but it's a public interface we provide which can
     override any lookup).

Then you iterate over the group lookups and switch test data and verify results.

I should be able to finish this next week, and that should provide some formal
automation to our regression testsuite.

This is orthogonal to the addition of this feature though.

Cheers,
Carlos.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-01-08  0:46             ` Stephen Gallagher
@ 2016-01-13  1:31               ` Carlos O'Donell
  2016-03-14 15:27                 ` Stephen Gallagher
  0 siblings, 1 reply; 33+ messages in thread
From: Carlos O'Donell @ 2016-01-13  1:31 UTC (permalink / raw)
  To: Stephen Gallagher, libc-alpha

On 01/07/2016 07:46 PM, Stephen Gallagher wrote:
> 2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>
> 
> 	[BZ #19072]
> 	* grp/Makefile (headers): Add grp-merge.h
> 	(routines): Add grp-merge.
> 	* grp/getgrgid_r.c: Include grp-merge.h.
> 	(DEEPCOPY_FN): Define.
> 	(MERGE_FN): Define.
> 	* grp/getgrname_r.c: Include grp-merge.h.
> 	(DEEPCOPY_FN): Define.
> 	(MERGE_FN): Define.
> 	* grp/grp-merge.c: New file.
> 	* grp/grp-merge.h: New file.
> 	* manual/nss.texi (Actions in the NSS configuration): Describe
> 	return, continue, and merge.
> 	* nscd/Makefile: Add vpath to find grp-merge.c
> 	(nscd-modules): Add grp-merge.
> 	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
> 	(DEEPCOPY_FN): Define.
> 	(MERGE_FN): Define.
> 	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
> 	(DEEPCOPY_FN): Define.
> 	(MERGE_FN): Define.
> 	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
> 	[!MERGE_FN]: Define __merge_einval.
> 	(CHECK_MERGE): Define.
> 	(REENTRANT_NAME): Process merge if do_merge is true.
> 	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
> 	(__nss_getent_r): Likewise.
> 	* nss/nsswitch.c (nss_parse_service_list): Likewise.
> 	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

This patch looks good to me.

I reviewed this several times internally at Red Hat, and I think you
must have sent out the wrong version in the initial post because I'm
pretty sure I hammered out all the trivial code formatting issues
including the comment formatting :-)

Architecturally the patch is a good solution, and the merge buffers
are done in such a way that the new feature is clean and easy to
implement.

From an implementation perspective I think the documentation of the
feature and the implementation are done and well tested by you in your
environment, and we're working on a broader set of tests which will
provide regression testing for this feature. It's not yet done, but
it uses completely uncontroversial features like __nss_configure_lookup
and two test NSS modules.

I think this should do into 2.23 as a new feature before the freeze.

It has little to no risk because it is not a public ABI/API issue.

The scenario for systems with [SUCCESS=merge] without this support
is fail-safe, and it limits the lookup scope.

I'll propose to Adhemerval that we merge this for 2.23.

Cheers,
Carlos.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2015-12-29 21:58 ` Mike Frysinger
  2016-01-04 14:10   ` Stephen Gallagher
  2016-01-13  1:24   ` Carlos O'Donell
@ 2016-01-20 20:32   ` Carlos O'Donell
  2 siblings, 0 replies; 33+ messages in thread
From: Carlos O'Donell @ 2016-01-20 20:32 UTC (permalink / raw)
  To: Stephen Gallagher, Mike Frysinger; +Cc: libc-alpha

On 12/29/2015 04:58 PM, Mike Frysinger wrote:
> On 16 Dec 2015 10:11, Stephen Gallagher wrote:
>> == Justification ==
>> It is common today for users to rely on centrally-managed user stores for
>> handling their user accounts. However, much software existing today does
>> not have an innate understanding of such accounts. Instead, they commonly
>> rely on membership in known groups for managing access-control (for
>> example the "wheel" group on Fedora and RHEL systems or the "adm" group
>> on Debian-derived systems). In the present incarnation of nsswitch, the
>> only way to have such groups managed by a remote user store such as
>> FreeIPA or Active Directory would be to manually remove the groups from
>> /etc/group on the clients so that nsswitch would then move past nss_files
>> and into the SSSD, nss-ldap or other remote user database.
> 
> you've lost me.  the whole point of nsswitch.conf is to let the admin
> explicitly control the order and precedence of look up sources.  so if
> you want to look up other sources, fix your /etc/nsswitch.conf to list
> the remote sources first over /etc/groups.

Mike,

Did Stephen's response answer your question?

Nobody has objected to the new "merge" functionality, and to be honest,
because it has no ABI/API impact we could add it without any impact to
late stage testing.

I'd like to see this feature in 2.23, but would also like to see some
review that people think this is a good solution.

Cheers,
Carlos.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-01-13  1:31               ` Carlos O'Donell
@ 2016-03-14 15:27                 ` Stephen Gallagher
  0 siblings, 0 replies; 33+ messages in thread
From: Stephen Gallagher @ 2016-03-14 15:27 UTC (permalink / raw)
  To: libc-alpha, vapier


[-- Attachment #1.1: Type: text/plain, Size: 2658 bytes --]

On 01/12/2016 08:31 PM, Carlos O'Donell wrote:
> On 01/07/2016 07:46 PM, Stephen Gallagher wrote:
>> 2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>
>>
>> 	[BZ #19072]
>> 	* grp/Makefile (headers): Add grp-merge.h
>> 	(routines): Add grp-merge.
>> 	* grp/getgrgid_r.c: Include grp-merge.h.
>> 	(DEEPCOPY_FN): Define.
>> 	(MERGE_FN): Define.
>> 	* grp/getgrname_r.c: Include grp-merge.h.
>> 	(DEEPCOPY_FN): Define.
>> 	(MERGE_FN): Define.
>> 	* grp/grp-merge.c: New file.
>> 	* grp/grp-merge.h: New file.
>> 	* manual/nss.texi (Actions in the NSS configuration): Describe
>> 	return, continue, and merge.
>> 	* nscd/Makefile: Add vpath to find grp-merge.c
>> 	(nscd-modules): Add grp-merge.
>> 	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
>> 	(DEEPCOPY_FN): Define.
>> 	(MERGE_FN): Define.
>> 	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
>> 	(DEEPCOPY_FN): Define.
>> 	(MERGE_FN): Define.
>> 	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
>> 	[!MERGE_FN]: Define __merge_einval.
>> 	(CHECK_MERGE): Define.
>> 	(REENTRANT_NAME): Process merge if do_merge is true.
>> 	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
>> 	(__nss_getent_r): Likewise.
>> 	* nss/nsswitch.c (nss_parse_service_list): Likewise.
>> 	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.
> 
> This patch looks good to me.
> 
> I reviewed this several times internally at Red Hat, and I think you
> must have sent out the wrong version in the initial post because I'm
> pretty sure I hammered out all the trivial code formatting issues
> including the comment formatting :-)
> 
> Architecturally the patch is a good solution, and the merge buffers
> are done in such a way that the new feature is clean and easy to
> implement.
> 
> From an implementation perspective I think the documentation of the
> feature and the implementation are done and well tested by you in your
> environment, and we're working on a broader set of tests which will
> provide regression testing for this feature. It's not yet done, but
> it uses completely uncontroversial features like __nss_configure_lookup
> and two test NSS modules.
> 
> I think this should do into 2.23 as a new feature before the freeze.
> 
> It has little to no risk because it is not a public ABI/API issue.
> 
> The scenario for systems with [SUCCESS=merge] without this support
> is fail-safe, and it limits the lookup scope.
> 
> I'll propose to Adhemerval that we merge this for 2.23.
> 
> Cheers,
> Carlos.
> 


Ping

I'd like to get this reviewed and merged in for 2.24 early on. Could someone
take another look?


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-01-04 14:12     ` Stephen Gallagher
  2016-01-04 15:42       ` Joseph Myers
@ 2016-03-22  0:40       ` Mike Frysinger
  2016-03-28 13:50         ` Stephen Gallagher
  1 sibling, 1 reply; 33+ messages in thread
From: Mike Frysinger @ 2016-03-22  0:40 UTC (permalink / raw)
  To: Stephen Gallagher; +Cc: libc-alpha

[-- Attachment #1: Type: text/plain, Size: 6613 bytes --]

On 04 Jan 2016 09:12, Stephen Gallagher wrote:

overall sounds fine.  i have no strong opinions as i don't use nss myself :).
style nits follow.

note: you should also update the NEWS file with a short blurb.
the commit message is great, albeit too verbose for NEWS.

> --- /dev/null
> +++ b/grp/grp-merge.c
> @@ -0,0 +1,177 @@
> +/* Group merging implementation.
> +   Copyright (C) 2015 Free Software Foundation, Inc.

date needs updating

> +#define BUFCHECK(size)		\
> +  do {				\
> +    if (c + size > buflen) {	\
> +        free (members);		\
> +        return ERANGE;		\
> +    }				\
> +  } while(0)

style is incorrect for GNU code.  perhaps (note use of tabs too):

#define BUFCHECK(size)		\
  do {				\
    if (c + (size) > buflen)	\
      {				\
	free (members);		\
	return ERANGE;		\
      }				\
  } while(0)

> +  /* Allocate a temporary holding area for the pointers to the member
> +     contents, including space for a NULL-terminator.  */
> +  members = malloc (sizeof (char *) * (memcount + 1));

do we have to worry about overflow ?  if there's some external logic that
keeps these from overflowing, might be nice to note somewhere (comes up a
few times in this patch).

> +  if (members == NULL)
> +      return ENOMEM;

indented by two too many spaces

> +  /* Copy the pointers from the members array into the buffer and assign them
> +     to the gr_mem member of destgrp.  */
> +  destgrp->gr_mem = (char **) &destbuf[c];
> +  len = sizeof (char *) * (memcount + 1);
> +  BUFCHECK (len);
> +  memcpy (&destbuf[c], members, len);
> +  c += len;
> +  free (members);
> +  members = NULL;

force of habit clearing members ?  seems pointless otherwise.

> +  if (endptr)
> +      *endptr = destbuf + c;

too much indentation

> +int
> +__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
> +	     size_t buflen, struct group *mergegrp, char *mergebuf)

same comments for this func as above since it seems to be largely the
same logic wise

> --- /dev/null
> +++ b/grp/grp-merge.h
> @@ -0,0 +1,35 @@
> +/* Group merging implementation.
> +   Copyright (C) 2015 Free Software Foundation, Inc.

update the date

> +/* Duplicate a grp struct (and its members). When no longer needed, the
> +   calling function must free(newbuf). */

two spaces after the .

> +/* Merge the member lists of two grp structs together. */

same here

> +int
> +__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
> +	     size_t buflen, struct group *mergegrp, char *mergebuf);

should these two funcs have internal_function & attribute_hidden applied
to them ?

> --- a/manual/nss.texi
> +++ b/manual/nss.texi
>
> +When processing @samp{merge} for @samp{group} membership, the group GID
> +and name must be identical for both entries. If only one or the other is

two spaces after the .

> +a match, the behavior is undefined.

could you clarify "undefined" ?  people could interpret this as memory
corruption / crashes, while others are are inconsistent results.  i think
we just want the latter.

> --- a/nss/getXXbyYY_r.c
> +++ b/nss/getXXbyYY_r.c
>
> +/* Set defaults for merge functions that haven't been defined.  */
> +#ifndef DEEPCOPY_FN
> +static inline int
> +__copy_einval (LOOKUP_TYPE a,
> +	      const size_t b,
> +	      LOOKUP_TYPE *c,
> +	      char *d,
> +	      char **e)

indentation of args looks slightly off

> +#ifndef MERGE_FN
> +static inline int
> +__merge_einval (LOOKUP_TYPE *a,
> +	       char *b,
> +	       char *c,
> +	       size_t d,
> +	       LOOKUP_TYPE *e,
> +	       char *f)

here too

> +#define CHECK_MERGE(err, status)	\
> +do {					\
> +  if (err)				\
> +    {					\
> +      __set_errno (err);		\
> +      if (err == ERANGE)		\
> +          status = NSS_STATUS_TRYAGAIN;	\
> +      else				\
> +          status = NSS_STATUS_UNAVAIL;	\
> +      break;				\
> +    }					\
> +} while(0)

almost there ;).  8 leading spaces -> 1 tab, too many indents for status
assignment, and a space before the ( in the "while (0)".

> +	  if (status == NSS_STATUS_SUCCESS)
> +	    {
> +		/* The previous loop saved a buffer for merging.
> +		   Perform the merge now.  */
> +		err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
> +				buffer);
> +		CHECK_MERGE (err,status);
> +		do_merge = 0;
> +	    }

indentation here is broken too -- 6 spaces, not 1 tab

> +	  else
> +	    {
> +	      /* If the result wasn't SUCCESS, copy the saved buffer back
> +	         into the result buffer and set the status back to
> +	         NSS_STATUS_SUCCESS to match the previous pass through the loop.
> +	          * If the next action is CONTINUE, it will overwrite the value
> +	            currently in the buffer and return the new value.
> +	          * If the next action is RETURN, we'll return the previously-
> +	            acquired values.
> +	          * If the next action is MERGE, then it will be added to the buffer
> +	            saved from the previous source.  */

some of these lines are too long

> +	  if (!mergebuf)

GNU style says to test values rather than rely on implicit bool.  so this
would be:
	if (mergebuf == NULL)

this comes up quite a bit in this patch

> +  free(mergebuf);

space before the (

> --- a/nss/getnssent_r.c
> +++ b/nss/getnssent_r.c
>
> +	{
> +	  /* This is a special-case. When [SUCCESS=merge] is in play,

two spaces after the .

> +	     _nss_next2() will skip to the next database.  Due to the
> +	     implementation of that function, we can't know whether we're
> +	     in an enumeration or an individual lookup, which behaves
> +	     differently with regards to merging.  We'll treat SUCCESS as
> +	     an indication to start the enumeration at this database.
> +	   */

the trailing */ should be on the previous line.

> +      else
> +	{
> +	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
> +	}

could elide the braces

> +	  if (status == NSS_STATUS_SUCCESS
> +	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
> +	    {
> +	      /* This is a special-case. When [SUCCESS=merge] is in play,

two spaces after the .

> +	         _nss_next2() will skip to the next database.  Due to the
> +	         implementation of that function, we can't know whether we're
> +	         in an enumeration or an individual lookup, which behaves
> +	         differently with regards to merging.  We'll treat SUCCESS as
> +	         an indication to return the results here.
> +	       */

cuddle up the */

> +	  else
> +	    {
> +	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
> +				     status, 0);
> +	    }

could elide the braces
-mike

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2015-12-16 15:11 [PATCH] nsswitch: Add group merging support Stephen Gallagher
  2015-12-29 21:58 ` Mike Frysinger
@ 2016-03-28 13:14 ` Florian Weimer
  1 sibling, 0 replies; 33+ messages in thread
From: Florian Weimer @ 2016-03-28 13:14 UTC (permalink / raw)
  To: Stephen Gallagher; +Cc: libc-alpha

On 12/16/2015 04:11 PM, Stephen Gallagher wrote:
> 	* nscd/Makefile: Add vpath to find grp-merge.c
> 	(nscd-modules): Add grp-merge.

Why is it necessary to compile grp-merge.c twice?

If you can use a single copy, you need to export it as GLIBC_PRIVATE.
Commit 8f5e8b01a1da2a207228f2072c934fa5918554b8 is a recent example
which shows how to do this (basically, edit the Versions file).  The
res_init_fp patch I posted has another example:

  https://sourceware.org/ml/libc-alpha/2016-02/msg00376.html

Thanks,
Florian

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-22  0:40       ` Mike Frysinger
@ 2016-03-28 13:50         ` Stephen Gallagher
  2016-03-28 13:51           ` Stephen Gallagher
  2016-03-28 19:40           ` Mike Frysinger
  0 siblings, 2 replies; 33+ messages in thread
From: Stephen Gallagher @ 2016-03-28 13:50 UTC (permalink / raw)
  To: libc-alpha


[-- Attachment #1.1: Type: text/plain, Size: 9660 bytes --]

On 03/21/2016 08:40 PM, Mike Frysinger wrote:
> On 04 Jan 2016 09:12, Stephen Gallagher wrote:
> 
> overall sounds fine.  i have no strong opinions as i don't use nss myself :).
> style nits follow.
> 
> note: you should also update the NEWS file with a short blurb.
> the commit message is great, albeit too verbose for NEWS.

Carlos told me to just put it in the == NEWS == section of the commit message
and that it would get added to the NEWS file when it got merged in.

> 
>> --- /dev/null
>> +++ b/grp/grp-merge.c
>> @@ -0,0 +1,177 @@
>> +/* Group merging implementation.
>> +   Copyright (C) 2015 Free Software Foundation, Inc.
> 
> date needs updating
> 


Hmm, it looks like I somehow managed to send out a really old patch rather than
the latest one. This is already fixed in the latest version.

>> +#define BUFCHECK(size)		\
>> +  do {				\
>> +    if (c + size > buflen) {	\
>> +        free (members);		\
>> +        return ERANGE;		\
>> +    }				\
>> +  } while(0)
> 
> style is incorrect for GNU code.  perhaps (note use of tabs too):
> 
> #define BUFCHECK(size)		\
>   do {				\
>     if (c + (size) > buflen)	\
>       {				\
> 	free (members);		\
> 	return ERANGE;		\
>       }				\
>   } while(0)
> 

Already fixed in the latest version.


>> +  /* Allocate a temporary holding area for the pointers to the member
>> +     contents, including space for a NULL-terminator.  */
>> +  members = malloc (sizeof (char *) * (memcount + 1));
> 
> do we have to worry about overflow ?  if there's some external logic that
> keeps these from overflowing, might be nice to note somewhere (comes up a
> few times in this patch).
> 

The memcount is determined immediately prior to this and the same loop-control
is used to fill it, so it should be impossible to overflow here.


>> +  if (members == NULL)
>> +      return ENOMEM;
> 
> indented by two too many spaces
> 


Already fixed in the latest version.

>> +  /* Copy the pointers from the members array into the buffer and assign them
>> +     to the gr_mem member of destgrp.  */
>> +  destgrp->gr_mem = (char **) &destbuf[c];
>> +  len = sizeof (char *) * (memcount + 1);
>> +  BUFCHECK (len);
>> +  memcpy (&destbuf[c], members, len);
>> +  c += len;
>> +  free (members);
>> +  members = NULL;
> 
> force of habit clearing members ?  seems pointless otherwise.
> 

Yes and no; I've found in the past that remembering to do this is helpful for
catching mistakes when refactoring in the future. (A NULL-dereference is far
easier to notice than bizarre garbage in a pointer). I can take it out if you
prefer, but it's unlikely to be harmful.

>> +  if (endptr)
>> +      *endptr = destbuf + c;
> 
> too much indentation

Already fixed in latest version.

> 
>> +int
>> +__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
>> +	     size_t buflen, struct group *mergegrp, char *mergebuf)
> 
> same comments for this func as above since it seems to be largely the
> same logic wise
> 

Same answers as above.


>> --- /dev/null
>> +++ b/grp/grp-merge.h
>> @@ -0,0 +1,35 @@
>> +/* Group merging implementation.
>> +   Copyright (C) 2015 Free Software Foundation, Inc.
> 
> update the date
> 

Already fixed in latest patch.

>> +/* Duplicate a grp struct (and its members). When no longer needed, the
>> +   calling function must free(newbuf). */
> 
> two spaces after the .
> 
>> +/* Merge the member lists of two grp structs together. */
> 
> same here
> 

Fixed both.


>> +int
>> +__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
>> +	     size_t buflen, struct group *mergegrp, char *mergebuf);
> 
> should these two funcs have internal_function & attribute_hidden applied
> to them ?
> 

I talked with Florian about this today. You're definitely right about
internal_function, but he also pointed out that I should add __merge_grp() and
__copy_grp() to the GLIBC_PRIVATE section of Versions and avoid compiling this
file twice (once for NSS and once for nscd).

New patch does so.


>> --- a/manual/nss.texi
>> +++ b/manual/nss.texi
>>
>> +When processing @samp{merge} for @samp{group} membership, the group GID
>> +and name must be identical for both entries. If only one or the other is
> 
> two spaces after the .
> 

Fixed.


>> +a match, the behavior is undefined.
> 
> could you clarify "undefined" ?  people could interpret this as memory
> corruption / crashes, while others are are inconsistent results.  i think
> we just want the latter.
> 

OK, the language "undefined" was suggested to me by Carlos. In reality, the
results are actually *consistent*, but they're consistently different depending
on which attribute was initially searched.

(Meaning getgrnam() will always return the same results and getgrgid() will
always return the same results, but they will not be the same as each other.)

As for "undefined", I think I'd actually prefer to keep it that way, because
it's a strong assertion that you should be careful not to do this. The external
effect to an inconsistent set of responses from NSS is likely to have
wide-ranging negative effects on the system. I'm perfectly content to have
"scary" language in the documentation to guard against that.


>> --- a/nss/getXXbyYY_r.c
>> +++ b/nss/getXXbyYY_r.c
>>
>> +/* Set defaults for merge functions that haven't been defined.  */
>> +#ifndef DEEPCOPY_FN
>> +static inline int
>> +__copy_einval (LOOKUP_TYPE a,
>> +	      const size_t b,
>> +	      LOOKUP_TYPE *c,
>> +	      char *d,
>> +	      char **e)
> 
> indentation of args looks slightly off
> 

Fixed


>> +#ifndef MERGE_FN
>> +static inline int
>> +__merge_einval (LOOKUP_TYPE *a,
>> +	       char *b,
>> +	       char *c,
>> +	       size_t d,
>> +	       LOOKUP_TYPE *e,
>> +	       char *f)
> 
> here too
> 

Fixed


>> +#define CHECK_MERGE(err, status)	\
>> +do {					\
>> +  if (err)				\
>> +    {					\
>> +      __set_errno (err);		\
>> +      if (err == ERANGE)		\
>> +          status = NSS_STATUS_TRYAGAIN;	\
>> +      else				\
>> +          status = NSS_STATUS_UNAVAIL;	\
>> +      break;				\
>> +    }					\
>> +} while(0)
> 
> almost there ;).  8 leading spaces -> 1 tab, too many indents for status
> assignment, and a space before the ( in the "while (0)".
> 

Fixed, I think. Mixing tabs and spaces hurts my brain.


>> +	  if (status == NSS_STATUS_SUCCESS)
>> +	    {
>> +		/* The previous loop saved a buffer for merging.
>> +		   Perform the merge now.  */
>> +		err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
>> +				buffer);
>> +		CHECK_MERGE (err,status);
>> +		do_merge = 0;
>> +	    }
> 
> indentation here is broken too -- 6 spaces, not 1 tab
> 

Fixed


>> +	  else
>> +	    {
>> +	      /* If the result wasn't SUCCESS, copy the saved buffer back
>> +	         into the result buffer and set the status back to
>> +	         NSS_STATUS_SUCCESS to match the previous pass through the loop.
>> +	          * If the next action is CONTINUE, it will overwrite the value
>> +	            currently in the buffer and return the new value.
>> +	          * If the next action is RETURN, we'll return the previously-
>> +	            acquired values.
>> +	          * If the next action is MERGE, then it will be added to the buffer
>> +	            saved from the previous source.  */
> 
> some of these lines are too long

Fixed

> 
>> +	  if (!mergebuf)
> 
> GNU style says to test values rather than rely on implicit bool.  so this
> would be:
> 	if (mergebuf == NULL)
> 
> this comes up quite a bit in this patch
> 

I see it only here, where else do you see it?


>> +  free(mergebuf);
> 
> space before the (
> 

Fixed

>> --- a/nss/getnssent_r.c
>> +++ b/nss/getnssent_r.c
>>
>> +	{
>> +	  /* This is a special-case. When [SUCCESS=merge] is in play,
> 
> two spaces after the .

Fixed.


> 
>> +	     _nss_next2() will skip to the next database.  Due to the
>> +	     implementation of that function, we can't know whether we're
>> +	     in an enumeration or an individual lookup, which behaves
>> +	     differently with regards to merging.  We'll treat SUCCESS as
>> +	     an indication to start the enumeration at this database.
>> +	   */
> 
> the trailing */ should be on the previous line.
> 

Fixed

>> +      else
>> +	{
>> +	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
>> +	}
> 
> could elide the braces
> 

I am aware, but my personal preference is to always have braces even for
single-line IF/ELSE blocks (helps avoid merge issues like the famous Apple GOTO
bug).


>> +	  if (status == NSS_STATUS_SUCCESS
>> +	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
>> +	    {
>> +	      /* This is a special-case. When [SUCCESS=merge] is in play,
> 
> two spaces after the .
> 

Fixed.

>> +	         _nss_next2() will skip to the next database.  Due to the
>> +	         implementation of that function, we can't know whether we're
>> +	         in an enumeration or an individual lookup, which behaves
>> +	         differently with regards to merging.  We'll treat SUCCESS as
>> +	         an indication to return the results here.
>> +	       */
> 
> cuddle up the */
> 

Fixed.


>> +	  else
>> +	    {
>> +	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
>> +				     status, 0);
>> +	    }
> 
> could elide the braces
> -mike
> 


See above.



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH] nsswitch: Add group merging support
  2016-03-28 13:50         ` Stephen Gallagher
@ 2016-03-28 13:51           ` Stephen Gallagher
  2016-03-28 13:54             ` Stephen Gallagher
  2016-03-28 19:40           ` Mike Frysinger
  1 sibling, 1 reply; 33+ messages in thread
From: Stephen Gallagher @ 2016-03-28 13:51 UTC (permalink / raw)
  To: libc-alpha

https://sourceware.org/glibc/wiki/Proposals/GroupMerging

== Justification ==
It is common today for users to rely on centrally-managed user stores for
handling their user accounts. However, much software existing today does
not have an innate understanding of such accounts. Instead, they commonly
rely on membership in known groups for managing access-control (for
example the "wheel" group on Fedora and RHEL systems or the "adm" group
on Debian-derived systems). In the present incarnation of nsswitch, the
only way to have such groups managed by a remote user store such as
FreeIPA or Active Directory would be to manually remove the groups from
/etc/group on the clients so that nsswitch would then move past nss_files
and into the SSSD, nss-ldap or other remote user database.

== Solution ==
With this patch, a new action is introduced for nsswitch:
NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
between two database entries in the nsswitch.conf file. When a group is
located in the first of the two group entries, processing will continue
on to the next one. If the group is also found in the next entry (and the
group name and GID are an exact match), the member list of the second
entry will be added to the group object to be returned.

== Implementation ==
After each DL_LOOKUP_FN() returns, the next action is checked. If the
function returned NSS_STATUS_SUCCESS and the next action is
NSS_ACTION_MERGE, a copy of the result buffer is saved for the next pass
through the loop. If on this next pass through the loop the database
returns another instance of a group matching both the group name and GID,
the member list is added to the previous list and it is returned as a
single object. If the following database does not contain the same group,
then the original is copied back into the destination buffer.

This patch implements merge functionality only for the group database.
For other databases, there is a default implementation that will return
the EINVAL errno if a merge is requested. The merge functionality can be
implemented for other databases at a later time if such is needed. Each
database must provide a unique implementation of the deep-copy and merge
functions.

If [SUCCESS=merge] is present in nsswitch.conf for a glibc version that
does not support it, glibc will process results up until that operation,
at which time it will return results if it has found them or else will
simply return an error. In practical terms, this ends up behaving like
the remainder of the nsswitch.conf line does not exist.

== Iterators ==
This feature does not modify the iterator functionality from its current
behavior. If getgrnam() or getgrgid() is called, glibc will iterate
through all entries in the `group` line in nsswitch.conf and display the
list of members without attempting to merge them. This is consistent with
the behavior of nss_files where if two separate lines are specified for
the same group in /etc/groups, getgrnam()/getgrgid() will display both.
Clients are already expected to handle this gracefully.

== No Premature Optimizations ==
The following is a list of places that might be eligible for
optimization, but were not overengineered for this initial contribution:
 * Any situation where a merge may occur will result in one malloc() of
   the same size as the input buffer.
 * Any situation where a merge does occur will result in a second
   malloc() to hold the list of pointers to member name strings.
 * The list of members is simply concatenated together and is not tested
   for uniqueness (which is identical to the behavior for nss_files,
   which will simply return identical values if they both exist on the
   line in the file. This could potentially be optimized to reduce space
   usage in the buffer, but it is both complex and computationally
   expensive to do so.

== Testing ==
I performed testing by running the getent utility against my newly-built
glibc and configuring /etc/nsswitch.conf with the following entry:
group: group:      files [SUCCESS=merge] sss

In /etc/group I included the line:
wheel:x:10:sgallagh

I then configured my local SSSD using the id_provider=local to respond
with:
wheel:*:10:localuser,localuser2

I then ran `getent group wheel` against the newly-built glibc in
multiple situations and received the expected output as described
above:
 * When SSSD was running.
 * When SSSD was configured in nsswitch.conf but the daemon was not
   running.
 * When SSSD was configured in nsswitch.conf but nss_sss.so.2 was not
   installed on the system.
 * When the order of 'sss' and 'files' was reversed.
 * All of the above with the [SUCCESS=merge] removed (to ensure no
   regressions).
 * All of the above with `getent group 10`.
 * All of the above with `getent group` with and without
   `enumerate=true` set in SSSD.
 * All of the above with and without nscd enabled on the system.

== NEWS ==

* A new NSS action is added to facilitate large distribution system
  administration.  The action, MERGE, allows remote user stores like
  LDAP to be merged into local user stores like /etc/groups in order
  to provide easy to use, updated, and managed sets of merged
  credentials.  The new action can be used by configuring it in
  /etc/nsswitch.conf:
  group: files [SUCCESS=merge] nis
  Implemented by Stephen Gallagher (Red Hat).

== ChangeLog ==

2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>

	[BZ #19072]
	* grp/Makefile (headers): Add grp-merge.h
	(routines): Add grp-merge.
	* grp/getgrgid_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/getgrname_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/grp-merge.c: New file.
	* grp/grp-merge.h: New file.
	* manual/nss.texi (Actions in the NSS configuration): Describe
	return, continue, and merge.
	* nscd/Makefile: Add vpath to find grp-merge.c
	(nscd-modules): Add grp-merge.
	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
	[!MERGE_FN]: Define __merge_einval.
	(CHECK_MERGE): Define.
	(REENTRANT_NAME): Process merge if do_merge is true.
	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
	(__nss_getent_r): Likewise.
	* nss/nsswitch.c (nss_parse_service_list): Likewise.
	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

Resolves BZ #19072
---
 grp/Makefile                      |   5 +-
 grp/Versions                      |   1 +
 grp/getgrgid_r.c                  |   3 +
 grp/getgrnam_r.c                  |   4 +
 grp/grp-merge.c                   | 180 ++++++++++++++++++++++++++++++++++++++
 grp/{getgrgid_r.c => grp-merge.h} |  26 ++++--
 manual/nss.texi                   |  46 +++++++++-
 nscd/getgrgid_r.c                 |   4 +
 nscd/getgrnam_r.c                 |   4 +
 nss/getXXbyYY_r.c                 | 106 +++++++++++++++++++++-
 nss/getnssent_r.c                 |  34 ++++++-
 nss/nsswitch.c                    |   3 +
 nss/nsswitch.h                    |   3 +-
 13 files changed, 401 insertions(+), 18 deletions(-)
 create mode 100644 grp/grp-merge.c
 copy grp/{getgrgid_r.c => grp-merge.h} (54%)

diff --git a/grp/Makefile b/grp/Makefile
index 4f1809cea55b7c2f407c4c527c5198509934e342..b4d52e2928668507ec59f3b23b8b97afd4645565 100644
--- a/grp/Makefile
+++ b/grp/Makefile
@@ -20,15 +20,16 @@
 #
 subdir	:= grp
 
 include ../Makeconfig
 
-headers := grp.h
+headers := grp.h grp-merge.h
 
 routines := fgetgrent initgroups setgroups \
 	    getgrent getgrgid getgrnam putgrent \
-	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r
+	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r \
+	    grp-merge
 
 tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
diff --git a/grp/Versions b/grp/Versions
index e01360da42ceed83b9f7ac2e51378ba9520e07d2..2e8ed2c08122dcc700485546924360f68fcae913 100644
--- a/grp/Versions
+++ b/grp/Versions
@@ -25,7 +25,8 @@ libc {
     getgrent_r; getgrgid_r; getgrnam_r;
   }
   GLIBC_2.2.4 {
     # g*
     getgrouplist;
+    __merge_grp; __copy_grp;
   }
 }
diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
index 9da834ac98d97e2bc83c3b989036c3222ff851c7..ed326c8424116373323bad01c27fad93f127d49b 100644
--- a/grp/getgrgid_r.c
+++ b/grp/getgrgid_r.c
@@ -16,14 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
 
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c
index 80960825c0a6ace837e720e47325b8899cc5c908..b8eb7cab9ca10abe56e5570817b0d719e1381d89 100644
--- a/grp/getgrnam_r.c
+++ b/grp/getgrnam_r.c
@@ -16,13 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/grp-merge.c b/grp/grp-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..398ee852fde3fad82caf6ac04c89bbc249951d4b
--- /dev/null
+++ b/grp/grp-merge.c
@@ -0,0 +1,180 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include "grp-merge.h"
+
+#define BUFCHECK(size)		\
+  do {						\
+    if (c + size > buflen)	\
+    {						\
+        free (members);		\
+        return ERANGE;		\
+    }						\
+  } while(0)
+
+int
+internal_function
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+{
+  size_t i;
+  size_t c = 0;
+  size_t len;
+  size_t memcount;
+  char **members = NULL;
+
+  /* Copy the GID.  */
+  destgrp->gr_gid = srcgrp.gr_gid;
+
+  /* Copy the name.  */
+  len = strlen (srcgrp.gr_name) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_name, len);
+  destgrp->gr_name = &destbuf[c];
+  c += len;
+
+  /* Copy the password.  */
+  len = strlen (srcgrp.gr_passwd) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+  destgrp->gr_passwd = &destbuf[c];
+  c += len;
+
+  /* Count all of the members.  */
+  for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+    ;
+
+  /* Allocate a temporary holding area for the pointers to the member
+     contents, including space for a NULL-terminator.  */
+  members = malloc (sizeof (char *) * (memcount + 1));
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy all of the group members to destbuf and add a pointer to each of
+     them into the 'members' array.  */
+  for (i = 0; srcgrp.gr_mem[i]; i++)
+    {
+      len = strlen (srcgrp.gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+      members[i] = &destbuf[c];
+      c += len;
+    }
+  members[i] = NULL;
+
+  /* Copy the pointers from the members array into the buffer and assign them
+     to the gr_mem member of destgrp.  */
+  destgrp->gr_mem = (char **) &destbuf[c];
+  len = sizeof (char *) * (memcount + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], members, len);
+  c += len;
+  free (members);
+  members = NULL;
+
+  /* Save the count of members at the end.  */
+  BUFCHECK (sizeof (size_t));
+  memcpy (&destbuf[c], &memcount, sizeof (size_t));
+  c += sizeof (size_t);
+
+  if (endptr)
+    *endptr = destbuf + c;
+  return 0;
+}
+
+/* Check that the name, GID and passwd fields match, then
+   copy in the gr_mem array.  */
+int
+internal_function
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+  size_t c, i, len;
+  size_t savedmemcount;
+  size_t memcount;
+  size_t membersize;
+  char **members = NULL;
+
+  /* We only support merging members of groups with identical names and
+     GID values. If we hit this case, we need to overwrite the current
+     buffer with the saved one (which is functionally equivalent to
+     treating the new lookup as NSS_STATUS NOTFOUND.  */
+  if (mergegrp->gr_gid != savedgrp->gr_gid
+      || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+    return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+
+  /* Get the count of group members from the last sizeof (size_t) bytes in the
+     mergegrp buffer.  */
+  savedmemcount = (size_t) *(savedend - sizeof (size_t));
+
+  /* Get the count of new members to add.  */
+  for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+    ;
+
+  /* Create a temporary array to hold the pointers to the member values from
+     both the saved and merge groups.  */
+  membersize = savedmemcount + memcount + 1;
+  members = malloc (sizeof (char *) * membersize);
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy in the existing member pointers from the saved group
+     Note: this is not NULL-terminated yet.  */
+  memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount);
+
+  /* Back up into the savedbuf until we get back to the NULL-terminator of the
+     group member list. (This means walking back savedmemcount + 1 (char *) pointers
+     and the member count value.
+     The value of c is going to be the used length of the buffer backed up by
+     the member count and further backed up by the size of the pointers.  */
+  c = savedend - savedbuf
+      - sizeof (size_t)
+      - sizeof (char *) * (savedmemcount + 1);
+
+  /* Add all the new group members, overwriting the old NULL-terminator while
+     adding the new pointers to the temporary array.  */
+  for (i = 0; mergegrp->gr_mem[i]; i++)
+    {
+      len = strlen (mergegrp->gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+      members[savedmemcount + i] = &savedbuf[c];
+      c += len;
+    }
+  /* Add the NULL-terminator.  */
+  members[savedmemcount + memcount] = NULL;
+
+  /* Copy the member array back into the buffer after the member list and free
+     the member array.  */
+  savedgrp->gr_mem = (char **) &savedbuf[c];
+  len = sizeof (char *) * membersize;
+  BUFCHECK (len);
+  memcpy (&savedbuf[c], members, len);
+  c += len;
+
+  free (members);
+  members = NULL;
+
+  /* Finally, copy the results back into mergebuf, since that's the buffer
+     that we were provided by the caller.  */
+  return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
diff --git a/grp/getgrgid_r.c b/grp/grp-merge.h
similarity index 54%
copy from grp/getgrgid_r.c
copy to grp/grp-merge.h
index 9da834ac98d97e2bc83c3b989036c3222ff851c7..969612b34dd389c0cc63913f1d32b52b2a2a75fd 100644
--- a/grp/getgrgid_r.c
+++ b/grp/grp-merge.h
@@ -1,8 +1,8 @@
-/* Copyright (C) 1996-2016 Free Software Foundation, Inc.
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
-   Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
 
    The GNU C Library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.
@@ -14,16 +14,24 @@
 
    You should have received a copy of the GNU Lesser General Public
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
 #include <grp.h>
 
+/* Duplicate a grp struct (and its members). When no longer needed, the
+   calling function must free(newbuf).  */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+	    internal_function;
 
-#define LOOKUP_TYPE	struct group
-#define FUNCTION_NAME	getgrgid
-#define DATABASE_NAME	group
-#define ADD_PARAMS	gid_t gid
-#define ADD_VARIABLES	gid
-#define BUFLEN		NSS_BUFLEN_GROUP
+/* Merge the member lists of two grp structs together.  */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+	     internal_function;
 
-#include <nss/getXXbyYY_r.c>
+#endif /* _GRP_MERGE_H */
diff --git a/manual/nss.texi b/manual/nss.texi
index 66dcceffe01f225f078e88dd006bb90e80c85723..ddc16026c0bd57f54225ef57cb9cc0a337e400d1 100644
--- a/manual/nss.texi
+++ b/manual/nss.texi
@@ -178,11 +178,11 @@ where
 @var{action} @result{} return | continue
 @end smallexample
 
 The case of the keywords is insignificant.  The @var{status}
 values are the results of a call to a lookup function of a specific
-service.  They mean
+service.  They mean:
 
 @ftable @samp
 @item success
 No error occurred and the wanted entry is returned.  The default action
 for this is @code{return}.
@@ -202,10 +202,54 @@ The service is temporarily unavailable.  This could mean a file is
 locked or a server currently cannot accept more connections.  The
 default action is @code{continue}.
 @end ftable
 
 @noindent
+The @var{action} values mean:
+
+@ftable @samp
+@item return
+
+If the status matches, stop the lookup process at this service
+specification.  If an entry is available, provide it to the application.
+If an error occurred, report it to the application.  In case of a prior
+@samp{merge} action, the data is combined with previous lookup results,
+as explained below.
+
+@item continue
+
+If the status matches, proceed with the lookup process at the next
+entry, discarding the result of the current lookup (and any merged
+data).  An exception is the @samp{initgroups} database and the
+@samp{success} status, where @samp{continue} acts like @code{merge}
+below.
+
+@item merge
+
+Proceed with the lookup process, retaining the current lookup result.
+This action is useful only with the @samp{success} status.  If a
+subsequent service lookup succeeds and has a matching @samp{return}
+specification, the results are merged, the lookup process ends, and the
+merged results are returned to the application.  If the following service
+has a matching @samp{merge} action, the lookup process continues,
+retaining the combined data from this and any previous lookups.
+
+After a @code{merge} action, errors from subsequent lookups are ignored,
+and the data gathered so far will be returned.
+
+The @samp{merge} only applies to the @samp{success} status.  It is
+currently implemented for the @samp{group} database and its group
+members field, @samp{gr_mem}.  If specified for other databases, it
+causes the lookup to fail (if the @var{status} matches).
+
+When processing @samp{merge} for @samp{group} membership, the group GID
+and name must be identical for both entries.  If only one or the other is
+a match, the behavior is undefined.
+
+@end ftable
+
+@noindent
 If we have a line like
 
 @smallexample
 ethers: nisplus [NOTFOUND=return] db files
 @end smallexample
diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c
index 8039f86ea842645aad144dd24ef75165eafd7a2b..de96262f55a50f08437eff735e80417cf6fd8041 100644
--- a/nscd/getgrgid_r.c
+++ b/nscd/getgrgid_r.c
@@ -15,17 +15,21 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c
index 67e4cd1e0aacaee6b8dabb4f4375493361a4620b..703c234a4d14d305a7868620fa662a59fef8cb65 100644
--- a/nscd/getgrnam_r.c
+++ b/nscd/getgrnam_r.c
@@ -15,16 +15,20 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 113c687e064a585fe8152d4ed1374dc0f8adb370..43b55d1a93fa204b59901b1d9ef9265035fad450 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -129,10 +129,52 @@
 # define AF_VAL af
 #else
 # define AF_VAL AF_INET
 #endif
 
+
+/* Set defaults for merge functions that haven't been defined.  */
+#ifndef DEEPCOPY_FN
+static inline int
+__copy_einval (LOOKUP_TYPE a,
+	       const size_t b,
+	       LOOKUP_TYPE *c,
+	       char *d,
+	       char **e)
+{
+  return EINVAL;
+}
+# define DEEPCOPY_FN __copy_einval
+#endif
+
+#ifndef MERGE_FN
+static inline int
+__merge_einval (LOOKUP_TYPE *a,
+		char *b,
+		char *c,
+		size_t d,
+		LOOKUP_TYPE *e,
+		char *f)
+{
+  return EINVAL;
+}
+# define MERGE_FN __merge_einval
+#endif
+
+#define CHECK_MERGE(err, status)	\
+do {					\
+  if (err)				\
+    {					\
+      __set_errno (err);		\
+      if (err == ERANGE)		\
+	status = NSS_STATUS_TRYAGAIN;	\
+      else				\
+	status = NSS_STATUS_UNAVAIL;	\
+      break;				\
+    }					\
+} while (0)
+
 /* Type of the lookup function we need here.  */
 typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *,
 					    size_t, int * H_ERRNO_PARM
 					    EXTRA_PARAMS);
 
@@ -150,17 +192,20 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 {
   static bool startp_initialized;
   static service_user *startp;
   static lookup_function start_fct;
   service_user *nip;
+  int do_merge = 0;
+  LOOKUP_TYPE mergegrp;
+  char *mergebuf = NULL;
+  char *endptr = NULL;
   union
   {
     lookup_function l;
     void *ptr;
   } fct;
-
-  int no_more;
+  int no_more, err;
   enum nss_status status = NSS_STATUS_UNAVAIL;
 #ifdef USE_NSCD
   int nscd_status;
 #endif
 #ifdef NEED_H_ERRNO
@@ -276,13 +321,70 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && *h_errnop == NETDB_INTERNAL
 #endif
 	  && errno == ERANGE)
 	break;
 
+      if (do_merge)
+	{
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+	      /* The previous loop saved a buffer for merging.
+		 Perform the merge now.  */
+	      err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
+			      buffer);
+	      CHECK_MERGE (err,status);
+	      do_merge = 0;
+	    }
+	  else
+	    {
+	      /* If the result wasn't SUCCESS, copy the saved buffer back
+	         into the result buffer and set the status back to
+	         NSS_STATUS_SUCCESS to match the previous pass through the
+	         loop.
+	          * If the next action is CONTINUE, it will overwrite the value
+	            currently in the buffer and return the new value.
+	          * If the next action is RETURN, we'll return the previously-
+	            acquired values.
+	          * If the next action is MERGE, then it will be added to the
+	            buffer saved from the previous source.  */
+	      err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL);
+	      CHECK_MERGE (err, status);
+	      status = NSS_STATUS_SUCCESS;
+	    }
+	}
+
+      /* If we were are configured to merge this value with the next one,
+         save the current value of the group struct.  */
+      if (nss_next_action (nip, status) == NSS_ACTION_MERGE
+	  && status == NSS_STATUS_SUCCESS)
+	{
+	  /* Copy the current values into a buffer to be merged with the next
+	     set of retrieved values.  */
+	  if (mergebuf == NULL)
+	    {
+	      /* Only allocate once and reuse it for as many merges as we need
+	         to perform.  */
+	      mergebuf = malloc (buflen);
+	      if (mergebuf == NULL)
+		{
+		  __set_errno (ENOMEM);
+		  status = NSS_STATUS_UNAVAIL;
+		  break;
+		}
+	    }
+
+	  err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr);
+	  CHECK_MERGE (err, status);
+	  do_merge = 1;
+	}
+
       no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING,
 			     REENTRANT2_NAME_STRING, &fct.ptr, status, 0);
     }
+  free (mergebuf);
+  mergebuf = NULL;
 
 #ifdef HANDLE_DIGITS_DOTS
 done:
 #endif
   *result = status == NSS_STATUS_SUCCESS ? resbuf : NULL;
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index 456907b018d44991f4020b0ee234fcfc4baeb1a0..e8a1f7574d1a6c5469b7ced9732f48e32818b8e6 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -77,11 +77,25 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       if (stayopen_tmp)
 	status = DL_CALL_FCT (fct.f, (*stayopen_tmp));
       else
 	status = DL_CALL_FCT (fct.f, (0));
 
-      no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	{
+	  /* This is a special-case.  When [SUCCESS=merge] is in play,
+	     _nss_next2() will skip to the next database.  Due to the
+	     implementation of that function, we can't know whether we're
+	     in an enumeration or an individual lookup, which behaves
+	     differently with regards to merging.  We'll treat SUCCESS as
+	     an indication to start the enumeration at this database. */
+	  no_more = 1;
+	}
+      else
+	{
+	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+	}
+
       if (is_last_nip)
 	*last_nip = *nip;
     }
 
   if (stayopen_tmp)
@@ -173,12 +187,26 @@ __nss_getent_r (const char *getent_func_name,
 	  && errno == ERANGE)
 	break;
 
       do
 	{
-	  no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
-				 status, 0);
+	  if (status == NSS_STATUS_SUCCESS
+	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	    {
+	      /* This is a special-case.  When [SUCCESS=merge] is in play,
+	         _nss_next2() will skip to the next database.  Due to the
+	         implementation of that function, we can't know whether we're
+	         in an enumeration or an individual lookup, which behaves
+	         differently with regards to merging.  We'll treat SUCCESS as
+	         an indication to return the results here. */
+	      no_more = 1;
+	    }
+	  else
+	    {
+	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
+				     status, 0);
+	    }
 
 	  if (is_last_nip)
 	    *last_nip = *nip;
 
 	  if (! no_more)
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index bb644cb373add1cf5c3bce854e7b38d16b77ba1d..d7706506f02886dbf63782f4af3d9f7242d309fd 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -710,10 +710,13 @@ nss_parse_service_list (const char *line)
 	      if (line - name == 6 && __strncasecmp (name, "RETURN", 6) == 0)
 		action = NSS_ACTION_RETURN;
 	      else if (line - name == 8
 		       && __strncasecmp (name, "CONTINUE", 8) == 0)
 		action = NSS_ACTION_CONTINUE;
+	      else if (line - name == 5
+		       && __strncasecmp (name, "MERGE", 5) == 0)
+		action = NSS_ACTION_MERGE;
 	      else
 		goto finish;
 
 	      if (not)
 		{
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index 0074ee1d655827e787ed5de296d5a6a421a001d8..54c8b656f70a8cb531483afc21bf93db0639bc42 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -30,11 +30,12 @@
 
 /* Actions performed after lookup finished.  */
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;
 
 
 typedef struct service_library
 {
-- 
2.7.3

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-28 13:51           ` Stephen Gallagher
@ 2016-03-28 13:54             ` Stephen Gallagher
  2016-03-28 13:55               ` Stephen Gallagher
  0 siblings, 1 reply; 33+ messages in thread
From: Stephen Gallagher @ 2016-03-28 13:54 UTC (permalink / raw)
  To: libc-alpha


[-- Attachment #1.1: Type: text/plain, Size: 257 bytes --]

On 03/28/2016 09:51 AM, Stephen Gallagher wrote:
> https://sourceware.org/glibc/wiki/Proposals/GroupMerging
> 


Oops, I accidentally sent a version that was generated with `git format-patch -M
-C` which doesn't look right for review. Resending.



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH] nsswitch: Add group merging support
  2016-03-28 13:54             ` Stephen Gallagher
@ 2016-03-28 13:55               ` Stephen Gallagher
  2016-03-28 17:16                 ` Florian Weimer
  0 siblings, 1 reply; 33+ messages in thread
From: Stephen Gallagher @ 2016-03-28 13:55 UTC (permalink / raw)
  To: libc-alpha

https://sourceware.org/glibc/wiki/Proposals/GroupMerging

== Justification ==
It is common today for users to rely on centrally-managed user stores for
handling their user accounts. However, much software existing today does
not have an innate understanding of such accounts. Instead, they commonly
rely on membership in known groups for managing access-control (for
example the "wheel" group on Fedora and RHEL systems or the "adm" group
on Debian-derived systems). In the present incarnation of nsswitch, the
only way to have such groups managed by a remote user store such as
FreeIPA or Active Directory would be to manually remove the groups from
/etc/group on the clients so that nsswitch would then move past nss_files
and into the SSSD, nss-ldap or other remote user database.

== Solution ==
With this patch, a new action is introduced for nsswitch:
NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
between two database entries in the nsswitch.conf file. When a group is
located in the first of the two group entries, processing will continue
on to the next one. If the group is also found in the next entry (and the
group name and GID are an exact match), the member list of the second
entry will be added to the group object to be returned.

== Implementation ==
After each DL_LOOKUP_FN() returns, the next action is checked. If the
function returned NSS_STATUS_SUCCESS and the next action is
NSS_ACTION_MERGE, a copy of the result buffer is saved for the next pass
through the loop. If on this next pass through the loop the database
returns another instance of a group matching both the group name and GID,
the member list is added to the previous list and it is returned as a
single object. If the following database does not contain the same group,
then the original is copied back into the destination buffer.

This patch implements merge functionality only for the group database.
For other databases, there is a default implementation that will return
the EINVAL errno if a merge is requested. The merge functionality can be
implemented for other databases at a later time if such is needed. Each
database must provide a unique implementation of the deep-copy and merge
functions.

If [SUCCESS=merge] is present in nsswitch.conf for a glibc version that
does not support it, glibc will process results up until that operation,
at which time it will return results if it has found them or else will
simply return an error. In practical terms, this ends up behaving like
the remainder of the nsswitch.conf line does not exist.

== Iterators ==
This feature does not modify the iterator functionality from its current
behavior. If getgrnam() or getgrgid() is called, glibc will iterate
through all entries in the `group` line in nsswitch.conf and display the
list of members without attempting to merge them. This is consistent with
the behavior of nss_files where if two separate lines are specified for
the same group in /etc/groups, getgrnam()/getgrgid() will display both.
Clients are already expected to handle this gracefully.

== No Premature Optimizations ==
The following is a list of places that might be eligible for
optimization, but were not overengineered for this initial contribution:
 * Any situation where a merge may occur will result in one malloc() of
   the same size as the input buffer.
 * Any situation where a merge does occur will result in a second
   malloc() to hold the list of pointers to member name strings.
 * The list of members is simply concatenated together and is not tested
   for uniqueness (which is identical to the behavior for nss_files,
   which will simply return identical values if they both exist on the
   line in the file. This could potentially be optimized to reduce space
   usage in the buffer, but it is both complex and computationally
   expensive to do so.

== Testing ==
I performed testing by running the getent utility against my newly-built
glibc and configuring /etc/nsswitch.conf with the following entry:
group: group:      files [SUCCESS=merge] sss

In /etc/group I included the line:
wheel:x:10:sgallagh

I then configured my local SSSD using the id_provider=local to respond
with:
wheel:*:10:localuser,localuser2

I then ran `getent group wheel` against the newly-built glibc in
multiple situations and received the expected output as described
above:
 * When SSSD was running.
 * When SSSD was configured in nsswitch.conf but the daemon was not
   running.
 * When SSSD was configured in nsswitch.conf but nss_sss.so.2 was not
   installed on the system.
 * When the order of 'sss' and 'files' was reversed.
 * All of the above with the [SUCCESS=merge] removed (to ensure no
   regressions).
 * All of the above with `getent group 10`.
 * All of the above with `getent group` with and without
   `enumerate=true` set in SSSD.
 * All of the above with and without nscd enabled on the system.

== NEWS ==

* A new NSS action is added to facilitate large distribution system
  administration.  The action, MERGE, allows remote user stores like
  LDAP to be merged into local user stores like /etc/groups in order
  to provide easy to use, updated, and managed sets of merged
  credentials.  The new action can be used by configuring it in
  /etc/nsswitch.conf:
  group: files [SUCCESS=merge] nis
  Implemented by Stephen Gallagher (Red Hat).

== ChangeLog ==

2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>

	[BZ #19072]
	* grp/Makefile (headers): Add grp-merge.h
	(routines): Add grp-merge.
	* grp/getgrgid_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/getgrname_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/grp-merge.c: New file.
	* grp/grp-merge.h: New file.
	* manual/nss.texi (Actions in the NSS configuration): Describe
	return, continue, and merge.
	* nscd/Makefile: Add vpath to find grp-merge.c
	(nscd-modules): Add grp-merge.
	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
	[!MERGE_FN]: Define __merge_einval.
	(CHECK_MERGE): Define.
	(REENTRANT_NAME): Process merge if do_merge is true.
	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
	(__nss_getent_r): Likewise.
	* nss/nsswitch.c (nss_parse_service_list): Likewise.
	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

Resolves BZ #19072
---
 grp/Makefile      |   5 +-
 grp/Versions      |   1 +
 grp/getgrgid_r.c  |   3 +
 grp/getgrnam_r.c  |   4 ++
 grp/grp-merge.c   | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 grp/grp-merge.h   |  37 +++++++++++
 manual/nss.texi   |  46 +++++++++++++-
 nscd/getgrgid_r.c |   4 ++
 nscd/getgrnam_r.c |   4 ++
 nss/getXXbyYY_r.c | 106 +++++++++++++++++++++++++++++++-
 nss/getnssent_r.c |  34 ++++++++++-
 nss/nsswitch.c    |   3 +
 nss/nsswitch.h    |   3 +-
 13 files changed, 421 insertions(+), 9 deletions(-)
 create mode 100644 grp/grp-merge.c
 create mode 100644 grp/grp-merge.h

diff --git a/grp/Makefile b/grp/Makefile
index 4f1809cea55b7c2f407c4c527c5198509934e342..b4d52e2928668507ec59f3b23b8b97afd4645565 100644
--- a/grp/Makefile
+++ b/grp/Makefile
@@ -20,15 +20,16 @@
 #
 subdir	:= grp
 
 include ../Makeconfig
 
-headers := grp.h
+headers := grp.h grp-merge.h
 
 routines := fgetgrent initgroups setgroups \
 	    getgrent getgrgid getgrnam putgrent \
-	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r
+	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r \
+	    grp-merge
 
 tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
diff --git a/grp/Versions b/grp/Versions
index e01360da42ceed83b9f7ac2e51378ba9520e07d2..2e8ed2c08122dcc700485546924360f68fcae913 100644
--- a/grp/Versions
+++ b/grp/Versions
@@ -25,7 +25,8 @@ libc {
     getgrent_r; getgrgid_r; getgrnam_r;
   }
   GLIBC_2.2.4 {
     # g*
     getgrouplist;
+    __merge_grp; __copy_grp;
   }
 }
diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
index 9da834ac98d97e2bc83c3b989036c3222ff851c7..ed326c8424116373323bad01c27fad93f127d49b 100644
--- a/grp/getgrgid_r.c
+++ b/grp/getgrgid_r.c
@@ -16,14 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
 
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c
index 80960825c0a6ace837e720e47325b8899cc5c908..b8eb7cab9ca10abe56e5570817b0d719e1381d89 100644
--- a/grp/getgrnam_r.c
+++ b/grp/getgrnam_r.c
@@ -16,13 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/grp-merge.c b/grp/grp-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..398ee852fde3fad82caf6ac04c89bbc249951d4b
--- /dev/null
+++ b/grp/grp-merge.c
@@ -0,0 +1,180 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include "grp-merge.h"
+
+#define BUFCHECK(size)		\
+  do {						\
+    if (c + size > buflen)	\
+    {						\
+        free (members);		\
+        return ERANGE;		\
+    }						\
+  } while(0)
+
+int
+internal_function
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+{
+  size_t i;
+  size_t c = 0;
+  size_t len;
+  size_t memcount;
+  char **members = NULL;
+
+  /* Copy the GID.  */
+  destgrp->gr_gid = srcgrp.gr_gid;
+
+  /* Copy the name.  */
+  len = strlen (srcgrp.gr_name) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_name, len);
+  destgrp->gr_name = &destbuf[c];
+  c += len;
+
+  /* Copy the password.  */
+  len = strlen (srcgrp.gr_passwd) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+  destgrp->gr_passwd = &destbuf[c];
+  c += len;
+
+  /* Count all of the members.  */
+  for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+    ;
+
+  /* Allocate a temporary holding area for the pointers to the member
+     contents, including space for a NULL-terminator.  */
+  members = malloc (sizeof (char *) * (memcount + 1));
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy all of the group members to destbuf and add a pointer to each of
+     them into the 'members' array.  */
+  for (i = 0; srcgrp.gr_mem[i]; i++)
+    {
+      len = strlen (srcgrp.gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+      members[i] = &destbuf[c];
+      c += len;
+    }
+  members[i] = NULL;
+
+  /* Copy the pointers from the members array into the buffer and assign them
+     to the gr_mem member of destgrp.  */
+  destgrp->gr_mem = (char **) &destbuf[c];
+  len = sizeof (char *) * (memcount + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], members, len);
+  c += len;
+  free (members);
+  members = NULL;
+
+  /* Save the count of members at the end.  */
+  BUFCHECK (sizeof (size_t));
+  memcpy (&destbuf[c], &memcount, sizeof (size_t));
+  c += sizeof (size_t);
+
+  if (endptr)
+    *endptr = destbuf + c;
+  return 0;
+}
+
+/* Check that the name, GID and passwd fields match, then
+   copy in the gr_mem array.  */
+int
+internal_function
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+  size_t c, i, len;
+  size_t savedmemcount;
+  size_t memcount;
+  size_t membersize;
+  char **members = NULL;
+
+  /* We only support merging members of groups with identical names and
+     GID values. If we hit this case, we need to overwrite the current
+     buffer with the saved one (which is functionally equivalent to
+     treating the new lookup as NSS_STATUS NOTFOUND.  */
+  if (mergegrp->gr_gid != savedgrp->gr_gid
+      || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+    return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+
+  /* Get the count of group members from the last sizeof (size_t) bytes in the
+     mergegrp buffer.  */
+  savedmemcount = (size_t) *(savedend - sizeof (size_t));
+
+  /* Get the count of new members to add.  */
+  for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+    ;
+
+  /* Create a temporary array to hold the pointers to the member values from
+     both the saved and merge groups.  */
+  membersize = savedmemcount + memcount + 1;
+  members = malloc (sizeof (char *) * membersize);
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy in the existing member pointers from the saved group
+     Note: this is not NULL-terminated yet.  */
+  memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount);
+
+  /* Back up into the savedbuf until we get back to the NULL-terminator of the
+     group member list. (This means walking back savedmemcount + 1 (char *) pointers
+     and the member count value.
+     The value of c is going to be the used length of the buffer backed up by
+     the member count and further backed up by the size of the pointers.  */
+  c = savedend - savedbuf
+      - sizeof (size_t)
+      - sizeof (char *) * (savedmemcount + 1);
+
+  /* Add all the new group members, overwriting the old NULL-terminator while
+     adding the new pointers to the temporary array.  */
+  for (i = 0; mergegrp->gr_mem[i]; i++)
+    {
+      len = strlen (mergegrp->gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+      members[savedmemcount + i] = &savedbuf[c];
+      c += len;
+    }
+  /* Add the NULL-terminator.  */
+  members[savedmemcount + memcount] = NULL;
+
+  /* Copy the member array back into the buffer after the member list and free
+     the member array.  */
+  savedgrp->gr_mem = (char **) &savedbuf[c];
+  len = sizeof (char *) * membersize;
+  BUFCHECK (len);
+  memcpy (&savedbuf[c], members, len);
+  c += len;
+
+  free (members);
+  members = NULL;
+
+  /* Finally, copy the results back into mergebuf, since that's the buffer
+     that we were provided by the caller.  */
+  return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
diff --git a/grp/grp-merge.h b/grp/grp-merge.h
new file mode 100644
index 0000000000000000000000000000000000000000..969612b34dd389c0cc63913f1d32b52b2a2a75fd
--- /dev/null
+++ b/grp/grp-merge.h
@@ -0,0 +1,37 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
+#include <grp.h>
+
+/* Duplicate a grp struct (and its members). When no longer needed, the
+   calling function must free(newbuf).  */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+	    internal_function;
+
+/* Merge the member lists of two grp structs together.  */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+	     internal_function;
+
+#endif /* _GRP_MERGE_H */
diff --git a/manual/nss.texi b/manual/nss.texi
index 66dcceffe01f225f078e88dd006bb90e80c85723..ddc16026c0bd57f54225ef57cb9cc0a337e400d1 100644
--- a/manual/nss.texi
+++ b/manual/nss.texi
@@ -178,11 +178,11 @@ where
 @var{action} @result{} return | continue
 @end smallexample
 
 The case of the keywords is insignificant.  The @var{status}
 values are the results of a call to a lookup function of a specific
-service.  They mean
+service.  They mean:
 
 @ftable @samp
 @item success
 No error occurred and the wanted entry is returned.  The default action
 for this is @code{return}.
@@ -202,10 +202,54 @@ The service is temporarily unavailable.  This could mean a file is
 locked or a server currently cannot accept more connections.  The
 default action is @code{continue}.
 @end ftable
 
 @noindent
+The @var{action} values mean:
+
+@ftable @samp
+@item return
+
+If the status matches, stop the lookup process at this service
+specification.  If an entry is available, provide it to the application.
+If an error occurred, report it to the application.  In case of a prior
+@samp{merge} action, the data is combined with previous lookup results,
+as explained below.
+
+@item continue
+
+If the status matches, proceed with the lookup process at the next
+entry, discarding the result of the current lookup (and any merged
+data).  An exception is the @samp{initgroups} database and the
+@samp{success} status, where @samp{continue} acts like @code{merge}
+below.
+
+@item merge
+
+Proceed with the lookup process, retaining the current lookup result.
+This action is useful only with the @samp{success} status.  If a
+subsequent service lookup succeeds and has a matching @samp{return}
+specification, the results are merged, the lookup process ends, and the
+merged results are returned to the application.  If the following service
+has a matching @samp{merge} action, the lookup process continues,
+retaining the combined data from this and any previous lookups.
+
+After a @code{merge} action, errors from subsequent lookups are ignored,
+and the data gathered so far will be returned.
+
+The @samp{merge} only applies to the @samp{success} status.  It is
+currently implemented for the @samp{group} database and its group
+members field, @samp{gr_mem}.  If specified for other databases, it
+causes the lookup to fail (if the @var{status} matches).
+
+When processing @samp{merge} for @samp{group} membership, the group GID
+and name must be identical for both entries.  If only one or the other is
+a match, the behavior is undefined.
+
+@end ftable
+
+@noindent
 If we have a line like
 
 @smallexample
 ethers: nisplus [NOTFOUND=return] db files
 @end smallexample
diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c
index 8039f86ea842645aad144dd24ef75165eafd7a2b..de96262f55a50f08437eff735e80417cf6fd8041 100644
--- a/nscd/getgrgid_r.c
+++ b/nscd/getgrgid_r.c
@@ -15,17 +15,21 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c
index 67e4cd1e0aacaee6b8dabb4f4375493361a4620b..703c234a4d14d305a7868620fa662a59fef8cb65 100644
--- a/nscd/getgrnam_r.c
+++ b/nscd/getgrnam_r.c
@@ -15,16 +15,20 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 113c687e064a585fe8152d4ed1374dc0f8adb370..43b55d1a93fa204b59901b1d9ef9265035fad450 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -129,10 +129,52 @@
 # define AF_VAL af
 #else
 # define AF_VAL AF_INET
 #endif
 
+
+/* Set defaults for merge functions that haven't been defined.  */
+#ifndef DEEPCOPY_FN
+static inline int
+__copy_einval (LOOKUP_TYPE a,
+	       const size_t b,
+	       LOOKUP_TYPE *c,
+	       char *d,
+	       char **e)
+{
+  return EINVAL;
+}
+# define DEEPCOPY_FN __copy_einval
+#endif
+
+#ifndef MERGE_FN
+static inline int
+__merge_einval (LOOKUP_TYPE *a,
+		char *b,
+		char *c,
+		size_t d,
+		LOOKUP_TYPE *e,
+		char *f)
+{
+  return EINVAL;
+}
+# define MERGE_FN __merge_einval
+#endif
+
+#define CHECK_MERGE(err, status)	\
+do {					\
+  if (err)				\
+    {					\
+      __set_errno (err);		\
+      if (err == ERANGE)		\
+	status = NSS_STATUS_TRYAGAIN;	\
+      else				\
+	status = NSS_STATUS_UNAVAIL;	\
+      break;				\
+    }					\
+} while (0)
+
 /* Type of the lookup function we need here.  */
 typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *,
 					    size_t, int * H_ERRNO_PARM
 					    EXTRA_PARAMS);
 
@@ -150,17 +192,20 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 {
   static bool startp_initialized;
   static service_user *startp;
   static lookup_function start_fct;
   service_user *nip;
+  int do_merge = 0;
+  LOOKUP_TYPE mergegrp;
+  char *mergebuf = NULL;
+  char *endptr = NULL;
   union
   {
     lookup_function l;
     void *ptr;
   } fct;
-
-  int no_more;
+  int no_more, err;
   enum nss_status status = NSS_STATUS_UNAVAIL;
 #ifdef USE_NSCD
   int nscd_status;
 #endif
 #ifdef NEED_H_ERRNO
@@ -276,13 +321,70 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && *h_errnop == NETDB_INTERNAL
 #endif
 	  && errno == ERANGE)
 	break;
 
+      if (do_merge)
+	{
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+	      /* The previous loop saved a buffer for merging.
+		 Perform the merge now.  */
+	      err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
+			      buffer);
+	      CHECK_MERGE (err,status);
+	      do_merge = 0;
+	    }
+	  else
+	    {
+	      /* If the result wasn't SUCCESS, copy the saved buffer back
+	         into the result buffer and set the status back to
+	         NSS_STATUS_SUCCESS to match the previous pass through the
+	         loop.
+	          * If the next action is CONTINUE, it will overwrite the value
+	            currently in the buffer and return the new value.
+	          * If the next action is RETURN, we'll return the previously-
+	            acquired values.
+	          * If the next action is MERGE, then it will be added to the
+	            buffer saved from the previous source.  */
+	      err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL);
+	      CHECK_MERGE (err, status);
+	      status = NSS_STATUS_SUCCESS;
+	    }
+	}
+
+      /* If we were are configured to merge this value with the next one,
+         save the current value of the group struct.  */
+      if (nss_next_action (nip, status) == NSS_ACTION_MERGE
+	  && status == NSS_STATUS_SUCCESS)
+	{
+	  /* Copy the current values into a buffer to be merged with the next
+	     set of retrieved values.  */
+	  if (mergebuf == NULL)
+	    {
+	      /* Only allocate once and reuse it for as many merges as we need
+	         to perform.  */
+	      mergebuf = malloc (buflen);
+	      if (mergebuf == NULL)
+		{
+		  __set_errno (ENOMEM);
+		  status = NSS_STATUS_UNAVAIL;
+		  break;
+		}
+	    }
+
+	  err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr);
+	  CHECK_MERGE (err, status);
+	  do_merge = 1;
+	}
+
       no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING,
 			     REENTRANT2_NAME_STRING, &fct.ptr, status, 0);
     }
+  free (mergebuf);
+  mergebuf = NULL;
 
 #ifdef HANDLE_DIGITS_DOTS
 done:
 #endif
   *result = status == NSS_STATUS_SUCCESS ? resbuf : NULL;
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index 456907b018d44991f4020b0ee234fcfc4baeb1a0..e8a1f7574d1a6c5469b7ced9732f48e32818b8e6 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -77,11 +77,25 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       if (stayopen_tmp)
 	status = DL_CALL_FCT (fct.f, (*stayopen_tmp));
       else
 	status = DL_CALL_FCT (fct.f, (0));
 
-      no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	{
+	  /* This is a special-case.  When [SUCCESS=merge] is in play,
+	     _nss_next2() will skip to the next database.  Due to the
+	     implementation of that function, we can't know whether we're
+	     in an enumeration or an individual lookup, which behaves
+	     differently with regards to merging.  We'll treat SUCCESS as
+	     an indication to start the enumeration at this database. */
+	  no_more = 1;
+	}
+      else
+	{
+	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+	}
+
       if (is_last_nip)
 	*last_nip = *nip;
     }
 
   if (stayopen_tmp)
@@ -173,12 +187,26 @@ __nss_getent_r (const char *getent_func_name,
 	  && errno == ERANGE)
 	break;
 
       do
 	{
-	  no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
-				 status, 0);
+	  if (status == NSS_STATUS_SUCCESS
+	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	    {
+	      /* This is a special-case.  When [SUCCESS=merge] is in play,
+	         _nss_next2() will skip to the next database.  Due to the
+	         implementation of that function, we can't know whether we're
+	         in an enumeration or an individual lookup, which behaves
+	         differently with regards to merging.  We'll treat SUCCESS as
+	         an indication to return the results here. */
+	      no_more = 1;
+	    }
+	  else
+	    {
+	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
+				     status, 0);
+	    }
 
 	  if (is_last_nip)
 	    *last_nip = *nip;
 
 	  if (! no_more)
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index bb644cb373add1cf5c3bce854e7b38d16b77ba1d..d7706506f02886dbf63782f4af3d9f7242d309fd 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -710,10 +710,13 @@ nss_parse_service_list (const char *line)
 	      if (line - name == 6 && __strncasecmp (name, "RETURN", 6) == 0)
 		action = NSS_ACTION_RETURN;
 	      else if (line - name == 8
 		       && __strncasecmp (name, "CONTINUE", 8) == 0)
 		action = NSS_ACTION_CONTINUE;
+	      else if (line - name == 5
+		       && __strncasecmp (name, "MERGE", 5) == 0)
+		action = NSS_ACTION_MERGE;
 	      else
 		goto finish;
 
 	      if (not)
 		{
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index 0074ee1d655827e787ed5de296d5a6a421a001d8..54c8b656f70a8cb531483afc21bf93db0639bc42 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -30,11 +30,12 @@
 
 /* Actions performed after lookup finished.  */
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;
 
 
 typedef struct service_library
 {
-- 
2.7.3

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-28 13:55               ` Stephen Gallagher
@ 2016-03-28 17:16                 ` Florian Weimer
  2016-03-28 17:35                   ` Stephen Gallagher
  0 siblings, 1 reply; 33+ messages in thread
From: Florian Weimer @ 2016-03-28 17:16 UTC (permalink / raw)
  To: Stephen Gallagher; +Cc: libc-alpha

On 03/28/2016 03:55 PM, Stephen Gallagher wrote:
> --- a/grp/Versions
> +++ b/grp/Versions
> @@ -25,7 +25,8 @@ libc {
>      getgrent_r; getgrgid_r; getgrnam_r;
>    }
>    GLIBC_2.2.4 {
>      # g*
>      getgrouplist;
> +    __merge_grp; __copy_grp;
>    }
>  }

Did this really pass “make check”?  It should have failed the ABI test.

You need to use GLIBC_PRIVATE.  These functions are not intended to
become part of the public ABI.

Thanks,
Florian

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-28 17:16                 ` Florian Weimer
@ 2016-03-28 17:35                   ` Stephen Gallagher
  2016-03-28 17:36                     ` Stephen Gallagher
  0 siblings, 1 reply; 33+ messages in thread
From: Stephen Gallagher @ 2016-03-28 17:35 UTC (permalink / raw)
  To: Florian Weimer; +Cc: libc-alpha


[-- Attachment #1.1: Type: text/plain, Size: 709 bytes --]

On 03/28/2016 01:16 PM, Florian Weimer wrote:
> On 03/28/2016 03:55 PM, Stephen Gallagher wrote:
>> --- a/grp/Versions
>> +++ b/grp/Versions
>> @@ -25,7 +25,8 @@ libc {
>>      getgrent_r; getgrgid_r; getgrnam_r;
>>    }
>>    GLIBC_2.2.4 {
>>      # g*
>>      getgrouplist;
>> +    __merge_grp; __copy_grp;
>>    }
>>  }
> 
> Did this really pass “make check”?  It should have failed the ABI test.
> 
> You need to use GLIBC_PRIVATE.  These functions are not intended to
> become part of the public ABI.
> 
> Thanks,
> Florian
> 

Ugh, that was a mistake, clearly. I didn't run the full `make check` suite
there, I just built and confirmed that it still built and functioned.


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH] nsswitch: Add group merging support
  2016-03-28 17:35                   ` Stephen Gallagher
@ 2016-03-28 17:36                     ` Stephen Gallagher
  0 siblings, 0 replies; 33+ messages in thread
From: Stephen Gallagher @ 2016-03-28 17:36 UTC (permalink / raw)
  To: libc-alpha

https://sourceware.org/glibc/wiki/Proposals/GroupMerging

== Justification ==
It is common today for users to rely on centrally-managed user stores for
handling their user accounts. However, much software existing today does
not have an innate understanding of such accounts. Instead, they commonly
rely on membership in known groups for managing access-control (for
example the "wheel" group on Fedora and RHEL systems or the "adm" group
on Debian-derived systems). In the present incarnation of nsswitch, the
only way to have such groups managed by a remote user store such as
FreeIPA or Active Directory would be to manually remove the groups from
/etc/group on the clients so that nsswitch would then move past nss_files
and into the SSSD, nss-ldap or other remote user database.

== Solution ==
With this patch, a new action is introduced for nsswitch:
NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
between two database entries in the nsswitch.conf file. When a group is
located in the first of the two group entries, processing will continue
on to the next one. If the group is also found in the next entry (and the
group name and GID are an exact match), the member list of the second
entry will be added to the group object to be returned.

== Implementation ==
After each DL_LOOKUP_FN() returns, the next action is checked. If the
function returned NSS_STATUS_SUCCESS and the next action is
NSS_ACTION_MERGE, a copy of the result buffer is saved for the next pass
through the loop. If on this next pass through the loop the database
returns another instance of a group matching both the group name and GID,
the member list is added to the previous list and it is returned as a
single object. If the following database does not contain the same group,
then the original is copied back into the destination buffer.

This patch implements merge functionality only for the group database.
For other databases, there is a default implementation that will return
the EINVAL errno if a merge is requested. The merge functionality can be
implemented for other databases at a later time if such is needed. Each
database must provide a unique implementation of the deep-copy and merge
functions.

If [SUCCESS=merge] is present in nsswitch.conf for a glibc version that
does not support it, glibc will process results up until that operation,
at which time it will return results if it has found them or else will
simply return an error. In practical terms, this ends up behaving like
the remainder of the nsswitch.conf line does not exist.

== Iterators ==
This feature does not modify the iterator functionality from its current
behavior. If getgrnam() or getgrgid() is called, glibc will iterate
through all entries in the `group` line in nsswitch.conf and display the
list of members without attempting to merge them. This is consistent with
the behavior of nss_files where if two separate lines are specified for
the same group in /etc/groups, getgrnam()/getgrgid() will display both.
Clients are already expected to handle this gracefully.

== No Premature Optimizations ==
The following is a list of places that might be eligible for
optimization, but were not overengineered for this initial contribution:
 * Any situation where a merge may occur will result in one malloc() of
   the same size as the input buffer.
 * Any situation where a merge does occur will result in a second
   malloc() to hold the list of pointers to member name strings.
 * The list of members is simply concatenated together and is not tested
   for uniqueness (which is identical to the behavior for nss_files,
   which will simply return identical values if they both exist on the
   line in the file. This could potentially be optimized to reduce space
   usage in the buffer, but it is both complex and computationally
   expensive to do so.

== Testing ==
I performed testing by running the getent utility against my newly-built
glibc and configuring /etc/nsswitch.conf with the following entry:
group: group:      files [SUCCESS=merge] sss

In /etc/group I included the line:
wheel:x:10:sgallagh

I then configured my local SSSD using the id_provider=local to respond
with:
wheel:*:10:localuser,localuser2

I then ran `getent group wheel` against the newly-built glibc in
multiple situations and received the expected output as described
above:
 * When SSSD was running.
 * When SSSD was configured in nsswitch.conf but the daemon was not
   running.
 * When SSSD was configured in nsswitch.conf but nss_sss.so.2 was not
   installed on the system.
 * When the order of 'sss' and 'files' was reversed.
 * All of the above with the [SUCCESS=merge] removed (to ensure no
   regressions).
 * All of the above with `getent group 10`.
 * All of the above with `getent group` with and without
   `enumerate=true` set in SSSD.
 * All of the above with and without nscd enabled on the system.

== NEWS ==

* A new NSS action is added to facilitate large distribution system
  administration.  The action, MERGE, allows remote user stores like
  LDAP to be merged into local user stores like /etc/groups in order
  to provide easy to use, updated, and managed sets of merged
  credentials.  The new action can be used by configuring it in
  /etc/nsswitch.conf:
  group: files [SUCCESS=merge] nis
  Implemented by Stephen Gallagher (Red Hat).

== ChangeLog ==

2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>

	[BZ #19072]
	* grp/Makefile (headers): Add grp-merge.h
	(routines): Add grp-merge.
	* grp/getgrgid_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/getgrname_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/grp-merge.c: New file.
	* grp/grp-merge.h: New file.
	* manual/nss.texi (Actions in the NSS configuration): Describe
	return, continue, and merge.
	* nscd/Makefile: Add vpath to find grp-merge.c
	(nscd-modules): Add grp-merge.
	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
	[!MERGE_FN]: Define __merge_einval.
	(CHECK_MERGE): Define.
	(REENTRANT_NAME): Process merge if do_merge is true.
	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
	(__nss_getent_r): Likewise.
	* nss/nsswitch.c (nss_parse_service_list): Likewise.
	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

Resolves BZ #19072
---
 grp/Makefile      |   5 +-
 grp/Versions      |   3 +
 grp/getgrgid_r.c  |   3 +
 grp/getgrnam_r.c  |   4 ++
 grp/grp-merge.c   | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 grp/grp-merge.h   |  37 +++++++++++
 manual/nss.texi   |  46 +++++++++++++-
 nscd/getgrgid_r.c |   4 ++
 nscd/getgrnam_r.c |   4 ++
 nss/getXXbyYY_r.c | 106 +++++++++++++++++++++++++++++++-
 nss/getnssent_r.c |  34 ++++++++++-
 nss/nsswitch.c    |   3 +
 nss/nsswitch.h    |   3 +-
 13 files changed, 423 insertions(+), 9 deletions(-)
 create mode 100644 grp/grp-merge.c
 create mode 100644 grp/grp-merge.h

diff --git a/grp/Makefile b/grp/Makefile
index 4f1809cea55b7c2f407c4c527c5198509934e342..b4d52e2928668507ec59f3b23b8b97afd4645565 100644
--- a/grp/Makefile
+++ b/grp/Makefile
@@ -20,15 +20,16 @@
 #
 subdir	:= grp
 
 include ../Makeconfig
 
-headers := grp.h
+headers := grp.h grp-merge.h
 
 routines := fgetgrent initgroups setgroups \
 	    getgrent getgrgid getgrnam putgrent \
-	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r
+	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r \
+	    grp-merge
 
 tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
diff --git a/grp/Versions b/grp/Versions
index e01360da42ceed83b9f7ac2e51378ba9520e07d2..096caa47c50dead9c209e0cbdbe58c9ddcc11b55 100644
--- a/grp/Versions
+++ b/grp/Versions
@@ -26,6 +26,9 @@ libc {
   }
   GLIBC_2.2.4 {
     # g*
     getgrouplist;
   }
+  GLIBC_PRIVATE {
+    __merge_grp; __copy_grp;
+  }
 }
diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
index 9da834ac98d97e2bc83c3b989036c3222ff851c7..ed326c8424116373323bad01c27fad93f127d49b 100644
--- a/grp/getgrgid_r.c
+++ b/grp/getgrgid_r.c
@@ -16,14 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
 
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c
index 80960825c0a6ace837e720e47325b8899cc5c908..b8eb7cab9ca10abe56e5570817b0d719e1381d89 100644
--- a/grp/getgrnam_r.c
+++ b/grp/getgrnam_r.c
@@ -16,13 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/grp-merge.c b/grp/grp-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..398ee852fde3fad82caf6ac04c89bbc249951d4b
--- /dev/null
+++ b/grp/grp-merge.c
@@ -0,0 +1,180 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include "grp-merge.h"
+
+#define BUFCHECK(size)		\
+  do {						\
+    if (c + size > buflen)	\
+    {						\
+        free (members);		\
+        return ERANGE;		\
+    }						\
+  } while(0)
+
+int
+internal_function
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+{
+  size_t i;
+  size_t c = 0;
+  size_t len;
+  size_t memcount;
+  char **members = NULL;
+
+  /* Copy the GID.  */
+  destgrp->gr_gid = srcgrp.gr_gid;
+
+  /* Copy the name.  */
+  len = strlen (srcgrp.gr_name) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_name, len);
+  destgrp->gr_name = &destbuf[c];
+  c += len;
+
+  /* Copy the password.  */
+  len = strlen (srcgrp.gr_passwd) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+  destgrp->gr_passwd = &destbuf[c];
+  c += len;
+
+  /* Count all of the members.  */
+  for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+    ;
+
+  /* Allocate a temporary holding area for the pointers to the member
+     contents, including space for a NULL-terminator.  */
+  members = malloc (sizeof (char *) * (memcount + 1));
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy all of the group members to destbuf and add a pointer to each of
+     them into the 'members' array.  */
+  for (i = 0; srcgrp.gr_mem[i]; i++)
+    {
+      len = strlen (srcgrp.gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+      members[i] = &destbuf[c];
+      c += len;
+    }
+  members[i] = NULL;
+
+  /* Copy the pointers from the members array into the buffer and assign them
+     to the gr_mem member of destgrp.  */
+  destgrp->gr_mem = (char **) &destbuf[c];
+  len = sizeof (char *) * (memcount + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], members, len);
+  c += len;
+  free (members);
+  members = NULL;
+
+  /* Save the count of members at the end.  */
+  BUFCHECK (sizeof (size_t));
+  memcpy (&destbuf[c], &memcount, sizeof (size_t));
+  c += sizeof (size_t);
+
+  if (endptr)
+    *endptr = destbuf + c;
+  return 0;
+}
+
+/* Check that the name, GID and passwd fields match, then
+   copy in the gr_mem array.  */
+int
+internal_function
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+  size_t c, i, len;
+  size_t savedmemcount;
+  size_t memcount;
+  size_t membersize;
+  char **members = NULL;
+
+  /* We only support merging members of groups with identical names and
+     GID values. If we hit this case, we need to overwrite the current
+     buffer with the saved one (which is functionally equivalent to
+     treating the new lookup as NSS_STATUS NOTFOUND.  */
+  if (mergegrp->gr_gid != savedgrp->gr_gid
+      || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+    return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+
+  /* Get the count of group members from the last sizeof (size_t) bytes in the
+     mergegrp buffer.  */
+  savedmemcount = (size_t) *(savedend - sizeof (size_t));
+
+  /* Get the count of new members to add.  */
+  for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+    ;
+
+  /* Create a temporary array to hold the pointers to the member values from
+     both the saved and merge groups.  */
+  membersize = savedmemcount + memcount + 1;
+  members = malloc (sizeof (char *) * membersize);
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy in the existing member pointers from the saved group
+     Note: this is not NULL-terminated yet.  */
+  memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount);
+
+  /* Back up into the savedbuf until we get back to the NULL-terminator of the
+     group member list. (This means walking back savedmemcount + 1 (char *) pointers
+     and the member count value.
+     The value of c is going to be the used length of the buffer backed up by
+     the member count and further backed up by the size of the pointers.  */
+  c = savedend - savedbuf
+      - sizeof (size_t)
+      - sizeof (char *) * (savedmemcount + 1);
+
+  /* Add all the new group members, overwriting the old NULL-terminator while
+     adding the new pointers to the temporary array.  */
+  for (i = 0; mergegrp->gr_mem[i]; i++)
+    {
+      len = strlen (mergegrp->gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+      members[savedmemcount + i] = &savedbuf[c];
+      c += len;
+    }
+  /* Add the NULL-terminator.  */
+  members[savedmemcount + memcount] = NULL;
+
+  /* Copy the member array back into the buffer after the member list and free
+     the member array.  */
+  savedgrp->gr_mem = (char **) &savedbuf[c];
+  len = sizeof (char *) * membersize;
+  BUFCHECK (len);
+  memcpy (&savedbuf[c], members, len);
+  c += len;
+
+  free (members);
+  members = NULL;
+
+  /* Finally, copy the results back into mergebuf, since that's the buffer
+     that we were provided by the caller.  */
+  return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
diff --git a/grp/grp-merge.h b/grp/grp-merge.h
new file mode 100644
index 0000000000000000000000000000000000000000..969612b34dd389c0cc63913f1d32b52b2a2a75fd
--- /dev/null
+++ b/grp/grp-merge.h
@@ -0,0 +1,37 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
+#include <grp.h>
+
+/* Duplicate a grp struct (and its members). When no longer needed, the
+   calling function must free(newbuf).  */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+	    internal_function;
+
+/* Merge the member lists of two grp structs together.  */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+	     internal_function;
+
+#endif /* _GRP_MERGE_H */
diff --git a/manual/nss.texi b/manual/nss.texi
index 66dcceffe01f225f078e88dd006bb90e80c85723..ddc16026c0bd57f54225ef57cb9cc0a337e400d1 100644
--- a/manual/nss.texi
+++ b/manual/nss.texi
@@ -178,11 +178,11 @@ where
 @var{action} @result{} return | continue
 @end smallexample
 
 The case of the keywords is insignificant.  The @var{status}
 values are the results of a call to a lookup function of a specific
-service.  They mean
+service.  They mean:
 
 @ftable @samp
 @item success
 No error occurred and the wanted entry is returned.  The default action
 for this is @code{return}.
@@ -202,10 +202,54 @@ The service is temporarily unavailable.  This could mean a file is
 locked or a server currently cannot accept more connections.  The
 default action is @code{continue}.
 @end ftable
 
 @noindent
+The @var{action} values mean:
+
+@ftable @samp
+@item return
+
+If the status matches, stop the lookup process at this service
+specification.  If an entry is available, provide it to the application.
+If an error occurred, report it to the application.  In case of a prior
+@samp{merge} action, the data is combined with previous lookup results,
+as explained below.
+
+@item continue
+
+If the status matches, proceed with the lookup process at the next
+entry, discarding the result of the current lookup (and any merged
+data).  An exception is the @samp{initgroups} database and the
+@samp{success} status, where @samp{continue} acts like @code{merge}
+below.
+
+@item merge
+
+Proceed with the lookup process, retaining the current lookup result.
+This action is useful only with the @samp{success} status.  If a
+subsequent service lookup succeeds and has a matching @samp{return}
+specification, the results are merged, the lookup process ends, and the
+merged results are returned to the application.  If the following service
+has a matching @samp{merge} action, the lookup process continues,
+retaining the combined data from this and any previous lookups.
+
+After a @code{merge} action, errors from subsequent lookups are ignored,
+and the data gathered so far will be returned.
+
+The @samp{merge} only applies to the @samp{success} status.  It is
+currently implemented for the @samp{group} database and its group
+members field, @samp{gr_mem}.  If specified for other databases, it
+causes the lookup to fail (if the @var{status} matches).
+
+When processing @samp{merge} for @samp{group} membership, the group GID
+and name must be identical for both entries.  If only one or the other is
+a match, the behavior is undefined.
+
+@end ftable
+
+@noindent
 If we have a line like
 
 @smallexample
 ethers: nisplus [NOTFOUND=return] db files
 @end smallexample
diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c
index 8039f86ea842645aad144dd24ef75165eafd7a2b..de96262f55a50f08437eff735e80417cf6fd8041 100644
--- a/nscd/getgrgid_r.c
+++ b/nscd/getgrgid_r.c
@@ -15,17 +15,21 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c
index 67e4cd1e0aacaee6b8dabb4f4375493361a4620b..703c234a4d14d305a7868620fa662a59fef8cb65 100644
--- a/nscd/getgrnam_r.c
+++ b/nscd/getgrnam_r.c
@@ -15,16 +15,20 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 113c687e064a585fe8152d4ed1374dc0f8adb370..43b55d1a93fa204b59901b1d9ef9265035fad450 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -129,10 +129,52 @@
 # define AF_VAL af
 #else
 # define AF_VAL AF_INET
 #endif
 
+
+/* Set defaults for merge functions that haven't been defined.  */
+#ifndef DEEPCOPY_FN
+static inline int
+__copy_einval (LOOKUP_TYPE a,
+	       const size_t b,
+	       LOOKUP_TYPE *c,
+	       char *d,
+	       char **e)
+{
+  return EINVAL;
+}
+# define DEEPCOPY_FN __copy_einval
+#endif
+
+#ifndef MERGE_FN
+static inline int
+__merge_einval (LOOKUP_TYPE *a,
+		char *b,
+		char *c,
+		size_t d,
+		LOOKUP_TYPE *e,
+		char *f)
+{
+  return EINVAL;
+}
+# define MERGE_FN __merge_einval
+#endif
+
+#define CHECK_MERGE(err, status)	\
+do {					\
+  if (err)				\
+    {					\
+      __set_errno (err);		\
+      if (err == ERANGE)		\
+	status = NSS_STATUS_TRYAGAIN;	\
+      else				\
+	status = NSS_STATUS_UNAVAIL;	\
+      break;				\
+    }					\
+} while (0)
+
 /* Type of the lookup function we need here.  */
 typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *,
 					    size_t, int * H_ERRNO_PARM
 					    EXTRA_PARAMS);
 
@@ -150,17 +192,20 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 {
   static bool startp_initialized;
   static service_user *startp;
   static lookup_function start_fct;
   service_user *nip;
+  int do_merge = 0;
+  LOOKUP_TYPE mergegrp;
+  char *mergebuf = NULL;
+  char *endptr = NULL;
   union
   {
     lookup_function l;
     void *ptr;
   } fct;
-
-  int no_more;
+  int no_more, err;
   enum nss_status status = NSS_STATUS_UNAVAIL;
 #ifdef USE_NSCD
   int nscd_status;
 #endif
 #ifdef NEED_H_ERRNO
@@ -276,13 +321,70 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && *h_errnop == NETDB_INTERNAL
 #endif
 	  && errno == ERANGE)
 	break;
 
+      if (do_merge)
+	{
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+	      /* The previous loop saved a buffer for merging.
+		 Perform the merge now.  */
+	      err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
+			      buffer);
+	      CHECK_MERGE (err,status);
+	      do_merge = 0;
+	    }
+	  else
+	    {
+	      /* If the result wasn't SUCCESS, copy the saved buffer back
+	         into the result buffer and set the status back to
+	         NSS_STATUS_SUCCESS to match the previous pass through the
+	         loop.
+	          * If the next action is CONTINUE, it will overwrite the value
+	            currently in the buffer and return the new value.
+	          * If the next action is RETURN, we'll return the previously-
+	            acquired values.
+	          * If the next action is MERGE, then it will be added to the
+	            buffer saved from the previous source.  */
+	      err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL);
+	      CHECK_MERGE (err, status);
+	      status = NSS_STATUS_SUCCESS;
+	    }
+	}
+
+      /* If we were are configured to merge this value with the next one,
+         save the current value of the group struct.  */
+      if (nss_next_action (nip, status) == NSS_ACTION_MERGE
+	  && status == NSS_STATUS_SUCCESS)
+	{
+	  /* Copy the current values into a buffer to be merged with the next
+	     set of retrieved values.  */
+	  if (mergebuf == NULL)
+	    {
+	      /* Only allocate once and reuse it for as many merges as we need
+	         to perform.  */
+	      mergebuf = malloc (buflen);
+	      if (mergebuf == NULL)
+		{
+		  __set_errno (ENOMEM);
+		  status = NSS_STATUS_UNAVAIL;
+		  break;
+		}
+	    }
+
+	  err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr);
+	  CHECK_MERGE (err, status);
+	  do_merge = 1;
+	}
+
       no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING,
 			     REENTRANT2_NAME_STRING, &fct.ptr, status, 0);
     }
+  free (mergebuf);
+  mergebuf = NULL;
 
 #ifdef HANDLE_DIGITS_DOTS
 done:
 #endif
   *result = status == NSS_STATUS_SUCCESS ? resbuf : NULL;
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index 456907b018d44991f4020b0ee234fcfc4baeb1a0..e8a1f7574d1a6c5469b7ced9732f48e32818b8e6 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -77,11 +77,25 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       if (stayopen_tmp)
 	status = DL_CALL_FCT (fct.f, (*stayopen_tmp));
       else
 	status = DL_CALL_FCT (fct.f, (0));
 
-      no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	{
+	  /* This is a special-case.  When [SUCCESS=merge] is in play,
+	     _nss_next2() will skip to the next database.  Due to the
+	     implementation of that function, we can't know whether we're
+	     in an enumeration or an individual lookup, which behaves
+	     differently with regards to merging.  We'll treat SUCCESS as
+	     an indication to start the enumeration at this database. */
+	  no_more = 1;
+	}
+      else
+	{
+	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+	}
+
       if (is_last_nip)
 	*last_nip = *nip;
     }
 
   if (stayopen_tmp)
@@ -173,12 +187,26 @@ __nss_getent_r (const char *getent_func_name,
 	  && errno == ERANGE)
 	break;
 
       do
 	{
-	  no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
-				 status, 0);
+	  if (status == NSS_STATUS_SUCCESS
+	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	    {
+	      /* This is a special-case.  When [SUCCESS=merge] is in play,
+	         _nss_next2() will skip to the next database.  Due to the
+	         implementation of that function, we can't know whether we're
+	         in an enumeration or an individual lookup, which behaves
+	         differently with regards to merging.  We'll treat SUCCESS as
+	         an indication to return the results here. */
+	      no_more = 1;
+	    }
+	  else
+	    {
+	      no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
+				     status, 0);
+	    }
 
 	  if (is_last_nip)
 	    *last_nip = *nip;
 
 	  if (! no_more)
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index bb644cb373add1cf5c3bce854e7b38d16b77ba1d..d7706506f02886dbf63782f4af3d9f7242d309fd 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -710,10 +710,13 @@ nss_parse_service_list (const char *line)
 	      if (line - name == 6 && __strncasecmp (name, "RETURN", 6) == 0)
 		action = NSS_ACTION_RETURN;
 	      else if (line - name == 8
 		       && __strncasecmp (name, "CONTINUE", 8) == 0)
 		action = NSS_ACTION_CONTINUE;
+	      else if (line - name == 5
+		       && __strncasecmp (name, "MERGE", 5) == 0)
+		action = NSS_ACTION_MERGE;
 	      else
 		goto finish;
 
 	      if (not)
 		{
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index 0074ee1d655827e787ed5de296d5a6a421a001d8..54c8b656f70a8cb531483afc21bf93db0639bc42 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -30,11 +30,12 @@
 
 /* Actions performed after lookup finished.  */
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;
 
 
 typedef struct service_library
 {
-- 
2.7.3

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-28 13:50         ` Stephen Gallagher
  2016-03-28 13:51           ` Stephen Gallagher
@ 2016-03-28 19:40           ` Mike Frysinger
  2016-03-29  5:07             ` Florian Weimer
  1 sibling, 1 reply; 33+ messages in thread
From: Mike Frysinger @ 2016-03-28 19:40 UTC (permalink / raw)
  To: Stephen Gallagher; +Cc: libc-alpha

[-- Attachment #1: Type: text/plain, Size: 1771 bytes --]

On 28 Mar 2016 09:50, Stephen Gallagher wrote:
> On 03/21/2016 08:40 PM, Mike Frysinger wrote:
> > On 04 Jan 2016 09:12, Stephen Gallagher wrote:
> >> +a match, the behavior is undefined.
> > 
> > could you clarify "undefined" ?  people could interpret this as memory
> > corruption / crashes, while others are are inconsistent results.  i think
> > we just want the latter.
> 
> OK, the language "undefined" was suggested to me by Carlos. In reality, the
> results are actually *consistent*, but they're consistently different depending
> on which attribute was initially searched.
> 
> (Meaning getgrnam() will always return the same results and getgrgid() will
> always return the same results, but they will not be the same as each other.)
> 
> As for "undefined", I think I'd actually prefer to keep it that way, because
> it's a strong assertion that you should be careful not to do this. The external
> effect to an inconsistent set of responses from NSS is likely to have
> wide-ranging negative effects on the system. I'm perfectly content to have
> "scary" language in the documentation to guard against that.

in general i'm fine with "undefined", i would just prefer scoping it a
bit.  it's "undefined" in terms of the result set, but it's not in terms
of your program eating itself.

> >> +      else
> >> +	{
> >> +	  no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
> >> +	}
> > 
> > could elide the braces
> 
> I am aware, but my personal preference is to always have braces even for
> single-line IF/ELSE blocks (helps avoid merge issues like the famous Apple GOTO
> bug).

w/gcc-6 coming down the pike, i'd rather just go with relying on the new
warning flag about misleading indentation.
-mike

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-28 19:40           ` Mike Frysinger
@ 2016-03-29  5:07             ` Florian Weimer
  2016-03-31 19:32               ` Stephen Gallagher
  2016-04-01  2:00               ` Mike Frysinger
  0 siblings, 2 replies; 33+ messages in thread
From: Florian Weimer @ 2016-03-29  5:07 UTC (permalink / raw)
  To: Stephen Gallagher, libc-alpha

On 03/28/2016 09:40 PM, Mike Frysinger wrote:
> On 28 Mar 2016 09:50, Stephen Gallagher wrote:
>> On 03/21/2016 08:40 PM, Mike Frysinger wrote:
>>> On 04 Jan 2016 09:12, Stephen Gallagher wrote:
>>>> +a match, the behavior is undefined. +      else +	{ +
>>>> no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr,
>>>> status, 0); +	}
>>> 
>>> could elide the braces
>> 
>> I am aware, but my personal preference is to always have braces
>> even for single-line IF/ELSE blocks (helps avoid merge issues
>> like the famous Apple GOTO bug).

GNU style requires omitting the braces.

> w/gcc-6 coming down the pike, i'd rather just go with relying on
> the new warning flag about misleading indentation.

GCC 6 only warns about misleading additional indentation, not missing
indentation:

  <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66298>

So we aren't quite there yet.

Florian

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH] nsswitch: Add group merging support
  2016-03-29  5:07             ` Florian Weimer
@ 2016-03-31 19:32               ` Stephen Gallagher
  2016-04-04 13:17                 ` Stephen Gallagher
  2016-04-04 19:25                 ` Mike Frysinger
  2016-04-01  2:00               ` Mike Frysinger
  1 sibling, 2 replies; 33+ messages in thread
From: Stephen Gallagher @ 2016-03-31 19:32 UTC (permalink / raw)
  To: libc-alpha

https://sourceware.org/glibc/wiki/Proposals/GroupMerging

== Justification ==
It is common today for users to rely on centrally-managed user stores for
handling their user accounts. However, much software existing today does
not have an innate understanding of such accounts. Instead, they commonly
rely on membership in known groups for managing access-control (for
example the "wheel" group on Fedora and RHEL systems or the "adm" group
on Debian-derived systems). In the present incarnation of nsswitch, the
only way to have such groups managed by a remote user store such as
FreeIPA or Active Directory would be to manually remove the groups from
/etc/group on the clients so that nsswitch would then move past nss_files
and into the SSSD, nss-ldap or other remote user database.

== Solution ==
With this patch, a new action is introduced for nsswitch:
NSS_ACTION_MERGE. To take advantage of it, one will add [SUCCESS=merge]
between two database entries in the nsswitch.conf file. When a group is
located in the first of the two group entries, processing will continue
on to the next one. If the group is also found in the next entry (and the
group name and GID are an exact match), the member list of the second
entry will be added to the group object to be returned.

== Implementation ==
After each DL_LOOKUP_FN() returns, the next action is checked. If the
function returned NSS_STATUS_SUCCESS and the next action is
NSS_ACTION_MERGE, a copy of the result buffer is saved for the next pass
through the loop. If on this next pass through the loop the database
returns another instance of a group matching both the group name and GID,
the member list is added to the previous list and it is returned as a
single object. If the following database does not contain the same group,
then the original is copied back into the destination buffer.

This patch implements merge functionality only for the group database.
For other databases, there is a default implementation that will return
the EINVAL errno if a merge is requested. The merge functionality can be
implemented for other databases at a later time if such is needed. Each
database must provide a unique implementation of the deep-copy and merge
functions.

If [SUCCESS=merge] is present in nsswitch.conf for a glibc version that
does not support it, glibc will process results up until that operation,
at which time it will return results if it has found them or else will
simply return an error. In practical terms, this ends up behaving like
the remainder of the nsswitch.conf line does not exist.

== Iterators ==
This feature does not modify the iterator functionality from its current
behavior. If getgrnam() or getgrgid() is called, glibc will iterate
through all entries in the `group` line in nsswitch.conf and display the
list of members without attempting to merge them. This is consistent with
the behavior of nss_files where if two separate lines are specified for
the same group in /etc/groups, getgrnam()/getgrgid() will display both.
Clients are already expected to handle this gracefully.

== No Premature Optimizations ==
The following is a list of places that might be eligible for
optimization, but were not overengineered for this initial contribution:
 * Any situation where a merge may occur will result in one malloc() of
   the same size as the input buffer.
 * Any situation where a merge does occur will result in a second
   malloc() to hold the list of pointers to member name strings.
 * The list of members is simply concatenated together and is not tested
   for uniqueness (which is identical to the behavior for nss_files,
   which will simply return identical values if they both exist on the
   line in the file. This could potentially be optimized to reduce space
   usage in the buffer, but it is both complex and computationally
   expensive to do so.

== Testing ==
I performed testing by running the getent utility against my newly-built
glibc and configuring /etc/nsswitch.conf with the following entry:
group: group:      files [SUCCESS=merge] sss

In /etc/group I included the line:
wheel:x:10:sgallagh

I then configured my local SSSD using the id_provider=local to respond
with:
wheel:*:10:localuser,localuser2

I then ran `getent group wheel` against the newly-built glibc in
multiple situations and received the expected output as described
above:
 * When SSSD was running.
 * When SSSD was configured in nsswitch.conf but the daemon was not
   running.
 * When SSSD was configured in nsswitch.conf but nss_sss.so.2 was not
   installed on the system.
 * When the order of 'sss' and 'files' was reversed.
 * All of the above with the [SUCCESS=merge] removed (to ensure no
   regressions).
 * All of the above with `getent group 10`.
 * All of the above with `getent group` with and without
   `enumerate=true` set in SSSD.
 * All of the above with and without nscd enabled on the system.

== NEWS ==

* A new NSS action is added to facilitate large distribution system
  administration.  The action, MERGE, allows remote user stores like
  LDAP to be merged into local user stores like /etc/groups in order
  to provide easy to use, updated, and managed sets of merged
  credentials.  The new action can be used by configuring it in
  /etc/nsswitch.conf:
  group: files [SUCCESS=merge] nis
  Implemented by Stephen Gallagher (Red Hat).

== ChangeLog ==

2015-12-16  Stephen Gallagher  <sgallagh@redhat.com>

	[BZ #19072]
	* grp/Makefile (headers): Add grp-merge.h
	(routines): Add grp-merge.
	* grp/getgrgid_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/getgrname_r.c: Include grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* grp/grp-merge.c: New file.
	* grp/grp-merge.h: New file.
	* manual/nss.texi (Actions in the NSS configuration): Describe
	return, continue, and merge.
	* nscd/Makefile: Add vpath to find grp-merge.c
	(nscd-modules): Add grp-merge.
	* nscd/getgrgid_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nscd/getgrnam_r.c: Include grp/grp-merge.h.
	(DEEPCOPY_FN): Define.
	(MERGE_FN): Define.
	* nss/getXXbyYY_r.c [!DEEPCOPY_FN]: Define __copy_einval.
	[!MERGE_FN]: Define __merge_einval.
	(CHECK_MERGE): Define.
	(REENTRANT_NAME): Process merge if do_merge is true.
	* nss/getnssent_r.c (__nss_setent): Process NSS_ACTION_MERGE.
	(__nss_getent_r): Likewise.
	* nss/nsswitch.c (nss_parse_service_list): Likewise.
	* nss/nsswitch.h (lookup_actions): Define NSS_ACTION_MERGE.

Resolves BZ #19072
---
 grp/Makefile      |   5 +-
 grp/Versions      |   3 +
 grp/getgrgid_r.c  |   3 +
 grp/getgrnam_r.c  |   4 ++
 grp/grp-merge.c   | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 grp/grp-merge.h   |  37 +++++++++++
 manual/nss.texi   |  46 +++++++++++++-
 nscd/getgrgid_r.c |   4 ++
 nscd/getgrnam_r.c |   4 ++
 nss/getXXbyYY_r.c | 106 +++++++++++++++++++++++++++++++-
 nss/getnssent_r.c |  27 +++++++-
 nss/nsswitch.c    |   3 +
 nss/nsswitch.h    |   3 +-
 13 files changed, 416 insertions(+), 9 deletions(-)
 create mode 100644 grp/grp-merge.c
 create mode 100644 grp/grp-merge.h

diff --git a/grp/Makefile b/grp/Makefile
index 4f1809cea55b7c2f407c4c527c5198509934e342..b4d52e2928668507ec59f3b23b8b97afd4645565 100644
--- a/grp/Makefile
+++ b/grp/Makefile
@@ -20,15 +20,16 @@
 #
 subdir	:= grp
 
 include ../Makeconfig
 
-headers := grp.h
+headers := grp.h grp-merge.h
 
 routines := fgetgrent initgroups setgroups \
 	    getgrent getgrgid getgrnam putgrent \
-	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r
+	    getgrent_r getgrgid_r getgrnam_r fgetgrent_r \
+	    grp-merge
 
 tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
diff --git a/grp/Versions b/grp/Versions
index e01360da42ceed83b9f7ac2e51378ba9520e07d2..096caa47c50dead9c209e0cbdbe58c9ddcc11b55 100644
--- a/grp/Versions
+++ b/grp/Versions
@@ -26,6 +26,9 @@ libc {
   }
   GLIBC_2.2.4 {
     # g*
     getgrouplist;
   }
+  GLIBC_PRIVATE {
+    __merge_grp; __copy_grp;
+  }
 }
diff --git a/grp/getgrgid_r.c b/grp/getgrgid_r.c
index 9da834ac98d97e2bc83c3b989036c3222ff851c7..ed326c8424116373323bad01c27fad93f127d49b 100644
--- a/grp/getgrgid_r.c
+++ b/grp/getgrgid_r.c
@@ -16,14 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
 
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/getgrnam_r.c b/grp/getgrnam_r.c
index 80960825c0a6ace837e720e47325b8899cc5c908..b8eb7cab9ca10abe56e5570817b0d719e1381d89 100644
--- a/grp/getgrnam_r.c
+++ b/grp/getgrnam_r.c
@@ -16,13 +16,17 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 #include <nss/getXXbyYY_r.c>
diff --git a/grp/grp-merge.c b/grp/grp-merge.c
new file mode 100644
index 0000000000000000000000000000000000000000..398ee852fde3fad82caf6ac04c89bbc249951d4b
--- /dev/null
+++ b/grp/grp-merge.c
@@ -0,0 +1,180 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <grp.h>
+#include "grp-merge.h"
+
+#define BUFCHECK(size)		\
+  do {						\
+    if (c + size > buflen)	\
+    {						\
+        free (members);		\
+        return ERANGE;		\
+    }						\
+  } while(0)
+
+int
+internal_function
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+{
+  size_t i;
+  size_t c = 0;
+  size_t len;
+  size_t memcount;
+  char **members = NULL;
+
+  /* Copy the GID.  */
+  destgrp->gr_gid = srcgrp.gr_gid;
+
+  /* Copy the name.  */
+  len = strlen (srcgrp.gr_name) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_name, len);
+  destgrp->gr_name = &destbuf[c];
+  c += len;
+
+  /* Copy the password.  */
+  len = strlen (srcgrp.gr_passwd) + 1;
+  BUFCHECK (len);
+  memcpy (&destbuf[c], srcgrp.gr_passwd, len);
+  destgrp->gr_passwd = &destbuf[c];
+  c += len;
+
+  /* Count all of the members.  */
+  for (memcount = 0; srcgrp.gr_mem[memcount]; memcount++)
+    ;
+
+  /* Allocate a temporary holding area for the pointers to the member
+     contents, including space for a NULL-terminator.  */
+  members = malloc (sizeof (char *) * (memcount + 1));
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy all of the group members to destbuf and add a pointer to each of
+     them into the 'members' array.  */
+  for (i = 0; srcgrp.gr_mem[i]; i++)
+    {
+      len = strlen (srcgrp.gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&destbuf[c], srcgrp.gr_mem[i], len);
+      members[i] = &destbuf[c];
+      c += len;
+    }
+  members[i] = NULL;
+
+  /* Copy the pointers from the members array into the buffer and assign them
+     to the gr_mem member of destgrp.  */
+  destgrp->gr_mem = (char **) &destbuf[c];
+  len = sizeof (char *) * (memcount + 1);
+  BUFCHECK (len);
+  memcpy (&destbuf[c], members, len);
+  c += len;
+  free (members);
+  members = NULL;
+
+  /* Save the count of members at the end.  */
+  BUFCHECK (sizeof (size_t));
+  memcpy (&destbuf[c], &memcount, sizeof (size_t));
+  c += sizeof (size_t);
+
+  if (endptr)
+    *endptr = destbuf + c;
+  return 0;
+}
+
+/* Check that the name, GID and passwd fields match, then
+   copy in the gr_mem array.  */
+int
+internal_function
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+{
+  size_t c, i, len;
+  size_t savedmemcount;
+  size_t memcount;
+  size_t membersize;
+  char **members = NULL;
+
+  /* We only support merging members of groups with identical names and
+     GID values. If we hit this case, we need to overwrite the current
+     buffer with the saved one (which is functionally equivalent to
+     treating the new lookup as NSS_STATUS NOTFOUND.  */
+  if (mergegrp->gr_gid != savedgrp->gr_gid
+      || strcmp (mergegrp->gr_name, savedgrp->gr_name))
+    return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+
+  /* Get the count of group members from the last sizeof (size_t) bytes in the
+     mergegrp buffer.  */
+  savedmemcount = (size_t) *(savedend - sizeof (size_t));
+
+  /* Get the count of new members to add.  */
+  for (memcount = 0; mergegrp->gr_mem[memcount]; memcount++)
+    ;
+
+  /* Create a temporary array to hold the pointers to the member values from
+     both the saved and merge groups.  */
+  membersize = savedmemcount + memcount + 1;
+  members = malloc (sizeof (char *) * membersize);
+  if (members == NULL)
+    return ENOMEM;
+
+  /* Copy in the existing member pointers from the saved group
+     Note: this is not NULL-terminated yet.  */
+  memcpy (members, savedgrp->gr_mem, sizeof (char *) * savedmemcount);
+
+  /* Back up into the savedbuf until we get back to the NULL-terminator of the
+     group member list. (This means walking back savedmemcount + 1 (char *) pointers
+     and the member count value.
+     The value of c is going to be the used length of the buffer backed up by
+     the member count and further backed up by the size of the pointers.  */
+  c = savedend - savedbuf
+      - sizeof (size_t)
+      - sizeof (char *) * (savedmemcount + 1);
+
+  /* Add all the new group members, overwriting the old NULL-terminator while
+     adding the new pointers to the temporary array.  */
+  for (i = 0; mergegrp->gr_mem[i]; i++)
+    {
+      len = strlen (mergegrp->gr_mem[i]) + 1;
+      BUFCHECK (len);
+      memcpy (&savedbuf[c], mergegrp->gr_mem[i], len);
+      members[savedmemcount + i] = &savedbuf[c];
+      c += len;
+    }
+  /* Add the NULL-terminator.  */
+  members[savedmemcount + memcount] = NULL;
+
+  /* Copy the member array back into the buffer after the member list and free
+     the member array.  */
+  savedgrp->gr_mem = (char **) &savedbuf[c];
+  len = sizeof (char *) * membersize;
+  BUFCHECK (len);
+  memcpy (&savedbuf[c], members, len);
+  c += len;
+
+  free (members);
+  members = NULL;
+
+  /* Finally, copy the results back into mergebuf, since that's the buffer
+     that we were provided by the caller.  */
+  return __copy_grp (*savedgrp, buflen, mergegrp, mergebuf, NULL);
+}
diff --git a/grp/grp-merge.h b/grp/grp-merge.h
new file mode 100644
index 0000000000000000000000000000000000000000..969612b34dd389c0cc63913f1d32b52b2a2a75fd
--- /dev/null
+++ b/grp/grp-merge.h
@@ -0,0 +1,37 @@
+/* Group merging implementation.
+   Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GRP_MERGE_H
+#define _GRP_MERGE_H 1
+
+#include <grp.h>
+
+/* Duplicate a grp struct (and its members). When no longer needed, the
+   calling function must free(newbuf).  */
+int
+__copy_grp (const struct group srcgrp, const size_t buflen,
+	    struct group *destgrp, char *destbuf, char **endptr)
+	    internal_function;
+
+/* Merge the member lists of two grp structs together.  */
+int
+__merge_grp (struct group *savedgrp, char *savedbuf, char *savedend,
+	     size_t buflen, struct group *mergegrp, char *mergebuf)
+	     internal_function;
+
+#endif /* _GRP_MERGE_H */
diff --git a/manual/nss.texi b/manual/nss.texi
index 66dcceffe01f225f078e88dd006bb90e80c85723..ddc16026c0bd57f54225ef57cb9cc0a337e400d1 100644
--- a/manual/nss.texi
+++ b/manual/nss.texi
@@ -178,11 +178,11 @@ where
 @var{action} @result{} return | continue
 @end smallexample
 
 The case of the keywords is insignificant.  The @var{status}
 values are the results of a call to a lookup function of a specific
-service.  They mean
+service.  They mean:
 
 @ftable @samp
 @item success
 No error occurred and the wanted entry is returned.  The default action
 for this is @code{return}.
@@ -202,10 +202,54 @@ The service is temporarily unavailable.  This could mean a file is
 locked or a server currently cannot accept more connections.  The
 default action is @code{continue}.
 @end ftable
 
 @noindent
+The @var{action} values mean:
+
+@ftable @samp
+@item return
+
+If the status matches, stop the lookup process at this service
+specification.  If an entry is available, provide it to the application.
+If an error occurred, report it to the application.  In case of a prior
+@samp{merge} action, the data is combined with previous lookup results,
+as explained below.
+
+@item continue
+
+If the status matches, proceed with the lookup process at the next
+entry, discarding the result of the current lookup (and any merged
+data).  An exception is the @samp{initgroups} database and the
+@samp{success} status, where @samp{continue} acts like @code{merge}
+below.
+
+@item merge
+
+Proceed with the lookup process, retaining the current lookup result.
+This action is useful only with the @samp{success} status.  If a
+subsequent service lookup succeeds and has a matching @samp{return}
+specification, the results are merged, the lookup process ends, and the
+merged results are returned to the application.  If the following service
+has a matching @samp{merge} action, the lookup process continues,
+retaining the combined data from this and any previous lookups.
+
+After a @code{merge} action, errors from subsequent lookups are ignored,
+and the data gathered so far will be returned.
+
+The @samp{merge} only applies to the @samp{success} status.  It is
+currently implemented for the @samp{group} database and its group
+members field, @samp{gr_mem}.  If specified for other databases, it
+causes the lookup to fail (if the @var{status} matches).
+
+When processing @samp{merge} for @samp{group} membership, the group GID
+and name must be identical for both entries.  If only one or the other is
+a match, the behavior is undefined.
+
+@end ftable
+
+@noindent
 If we have a line like
 
 @smallexample
 ethers: nisplus [NOTFOUND=return] db files
 @end smallexample
diff --git a/nscd/getgrgid_r.c b/nscd/getgrgid_r.c
index 8039f86ea842645aad144dd24ef75165eafd7a2b..de96262f55a50f08437eff735e80417cf6fd8041 100644
--- a/nscd/getgrgid_r.c
+++ b/nscd/getgrgid_r.c
@@ -15,17 +15,21 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrgid
 #define DATABASE_NAME	group
 #define ADD_PARAMS	gid_t gid
 #define ADD_VARIABLES	gid
 #define BUFLEN		NSS_BUFLEN_GROUP
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nscd/getgrnam_r.c b/nscd/getgrnam_r.c
index 67e4cd1e0aacaee6b8dabb4f4375493361a4620b..703c234a4d14d305a7868620fa662a59fef8cb65 100644
--- a/nscd/getgrnam_r.c
+++ b/nscd/getgrnam_r.c
@@ -15,16 +15,20 @@
    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.  */
 
 #include <grp.h>
 
+#include "grp/grp-merge.h"
 
 #define LOOKUP_TYPE	struct group
 #define FUNCTION_NAME	getgrnam
 #define DATABASE_NAME	group
 #define ADD_PARAMS	const char *name
 #define ADD_VARIABLES	name
 
+#define DEEPCOPY_FN	__copy_grp
+#define MERGE_FN	__merge_grp
+
 /* We are nscd, so we don't want to be talking to ourselves.  */
 #undef	USE_NSCD
 
 #include <nss/getXXbyYY_r.c>
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 113c687e064a585fe8152d4ed1374dc0f8adb370..43b55d1a93fa204b59901b1d9ef9265035fad450 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -129,10 +129,52 @@
 # define AF_VAL af
 #else
 # define AF_VAL AF_INET
 #endif
 
+
+/* Set defaults for merge functions that haven't been defined.  */
+#ifndef DEEPCOPY_FN
+static inline int
+__copy_einval (LOOKUP_TYPE a,
+	       const size_t b,
+	       LOOKUP_TYPE *c,
+	       char *d,
+	       char **e)
+{
+  return EINVAL;
+}
+# define DEEPCOPY_FN __copy_einval
+#endif
+
+#ifndef MERGE_FN
+static inline int
+__merge_einval (LOOKUP_TYPE *a,
+		char *b,
+		char *c,
+		size_t d,
+		LOOKUP_TYPE *e,
+		char *f)
+{
+  return EINVAL;
+}
+# define MERGE_FN __merge_einval
+#endif
+
+#define CHECK_MERGE(err, status)	\
+do {					\
+  if (err)				\
+    {					\
+      __set_errno (err);		\
+      if (err == ERANGE)		\
+	status = NSS_STATUS_TRYAGAIN;	\
+      else				\
+	status = NSS_STATUS_UNAVAIL;	\
+      break;				\
+    }					\
+} while (0)
+
 /* Type of the lookup function we need here.  */
 typedef enum nss_status (*lookup_function) (ADD_PARAMS, LOOKUP_TYPE *, char *,
 					    size_t, int * H_ERRNO_PARM
 					    EXTRA_PARAMS);
 
@@ -150,17 +192,20 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 {
   static bool startp_initialized;
   static service_user *startp;
   static lookup_function start_fct;
   service_user *nip;
+  int do_merge = 0;
+  LOOKUP_TYPE mergegrp;
+  char *mergebuf = NULL;
+  char *endptr = NULL;
   union
   {
     lookup_function l;
     void *ptr;
   } fct;
-
-  int no_more;
+  int no_more, err;
   enum nss_status status = NSS_STATUS_UNAVAIL;
 #ifdef USE_NSCD
   int nscd_status;
 #endif
 #ifdef NEED_H_ERRNO
@@ -276,13 +321,70 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && *h_errnop == NETDB_INTERNAL
 #endif
 	  && errno == ERANGE)
 	break;
 
+      if (do_merge)
+	{
+
+	  if (status == NSS_STATUS_SUCCESS)
+	    {
+	      /* The previous loop saved a buffer for merging.
+		 Perform the merge now.  */
+	      err = MERGE_FN (&mergegrp, mergebuf, endptr, buflen, resbuf,
+			      buffer);
+	      CHECK_MERGE (err,status);
+	      do_merge = 0;
+	    }
+	  else
+	    {
+	      /* If the result wasn't SUCCESS, copy the saved buffer back
+	         into the result buffer and set the status back to
+	         NSS_STATUS_SUCCESS to match the previous pass through the
+	         loop.
+	          * If the next action is CONTINUE, it will overwrite the value
+	            currently in the buffer and return the new value.
+	          * If the next action is RETURN, we'll return the previously-
+	            acquired values.
+	          * If the next action is MERGE, then it will be added to the
+	            buffer saved from the previous source.  */
+	      err = DEEPCOPY_FN (mergegrp, buflen, resbuf, buffer, NULL);
+	      CHECK_MERGE (err, status);
+	      status = NSS_STATUS_SUCCESS;
+	    }
+	}
+
+      /* If we were are configured to merge this value with the next one,
+         save the current value of the group struct.  */
+      if (nss_next_action (nip, status) == NSS_ACTION_MERGE
+	  && status == NSS_STATUS_SUCCESS)
+	{
+	  /* Copy the current values into a buffer to be merged with the next
+	     set of retrieved values.  */
+	  if (mergebuf == NULL)
+	    {
+	      /* Only allocate once and reuse it for as many merges as we need
+	         to perform.  */
+	      mergebuf = malloc (buflen);
+	      if (mergebuf == NULL)
+		{
+		  __set_errno (ENOMEM);
+		  status = NSS_STATUS_UNAVAIL;
+		  break;
+		}
+	    }
+
+	  err = DEEPCOPY_FN (*resbuf, buflen, &mergegrp, mergebuf, &endptr);
+	  CHECK_MERGE (err, status);
+	  do_merge = 1;
+	}
+
       no_more = __nss_next2 (&nip, REENTRANT_NAME_STRING,
 			     REENTRANT2_NAME_STRING, &fct.ptr, status, 0);
     }
+  free (mergebuf);
+  mergebuf = NULL;
 
 #ifdef HANDLE_DIGITS_DOTS
 done:
 #endif
   *result = status == NSS_STATUS_SUCCESS ? resbuf : NULL;
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index 456907b018d44991f4020b0ee234fcfc4baeb1a0..f5092482ef2ab520ee896cca4907cbe40b8b14f3 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -77,11 +77,22 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       if (stayopen_tmp)
 	status = DL_CALL_FCT (fct.f, (*stayopen_tmp));
       else
 	status = DL_CALL_FCT (fct.f, (0));
 
-      no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+
+      /* This is a special-case.  When [SUCCESS=merge] is in play,
+         _nss_next2() will skip to the next database.  Due to the
+         implementation of that function, we can't know whether we're
+         in an enumeration or an individual lookup, which behaves
+         differently with regards to merging.  We'll treat SUCCESS as
+         an indication to start the enumeration at this database. */
+      if (nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	no_more = 1;
+      else
+	no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr, status, 0);
+
       if (is_last_nip)
 	*last_nip = *nip;
     }
 
   if (stayopen_tmp)
@@ -173,12 +184,22 @@ __nss_getent_r (const char *getent_func_name,
 	  && errno == ERANGE)
 	break;
 
       do
 	{
-	  no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
-				 status, 0);
+        /* This is a special-case.  When [SUCCESS=merge] is in play,
+           _nss_next2() will skip to the next database.  Due to the
+           implementation of that function, we can't know whether we're
+           in an enumeration or an individual lookup, which behaves
+           differently with regards to merging.  We'll treat SUCCESS as
+           an indication to return the results here. */
+	  if (status == NSS_STATUS_SUCCESS
+	      && nss_next_action (*nip, status) == NSS_ACTION_MERGE)
+	    no_more = 1;
+	  else
+	    no_more = __nss_next2 (nip, getent_func_name, NULL, &fct.ptr,
+				   status, 0);
 
 	  if (is_last_nip)
 	    *last_nip = *nip;
 
 	  if (! no_more)
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index bb644cb373add1cf5c3bce854e7b38d16b77ba1d..d7706506f02886dbf63782f4af3d9f7242d309fd 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -710,10 +710,13 @@ nss_parse_service_list (const char *line)
 	      if (line - name == 6 && __strncasecmp (name, "RETURN", 6) == 0)
 		action = NSS_ACTION_RETURN;
 	      else if (line - name == 8
 		       && __strncasecmp (name, "CONTINUE", 8) == 0)
 		action = NSS_ACTION_CONTINUE;
+	      else if (line - name == 5
+		       && __strncasecmp (name, "MERGE", 5) == 0)
+		action = NSS_ACTION_MERGE;
 	      else
 		goto finish;
 
 	      if (not)
 		{
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index 0074ee1d655827e787ed5de296d5a6a421a001d8..54c8b656f70a8cb531483afc21bf93db0639bc42 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -30,11 +30,12 @@
 
 /* Actions performed after lookup finished.  */
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;
 
 
 typedef struct service_library
 {
-- 
2.7.3

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-29  5:07             ` Florian Weimer
  2016-03-31 19:32               ` Stephen Gallagher
@ 2016-04-01  2:00               ` Mike Frysinger
  1 sibling, 0 replies; 33+ messages in thread
From: Mike Frysinger @ 2016-04-01  2:00 UTC (permalink / raw)
  To: Florian Weimer; +Cc: Stephen Gallagher, libc-alpha

[-- Attachment #1: Type: text/plain, Size: 1033 bytes --]

On 29 Mar 2016 07:07, Florian Weimer wrote:
> On 03/28/2016 09:40 PM, Mike Frysinger wrote:
> > On 28 Mar 2016 09:50, Stephen Gallagher wrote:
> >> On 03/21/2016 08:40 PM, Mike Frysinger wrote:
> >>> On 04 Jan 2016 09:12, Stephen Gallagher wrote:
> >>>> +a match, the behavior is undefined. +      else +	{ +
> >>>> no_more = __nss_next2 (nip, func_name, NULL, &fct.ptr,
> >>>> status, 0); +	}
> >>> 
> >>> could elide the braces
> >> 
> >> I am aware, but my personal preference is to always have braces
> >> even for single-line IF/ELSE blocks (helps avoid merge issues
> >> like the famous Apple GOTO bug).
> 
> GNU style requires omitting the braces.

easy enough :)

> > w/gcc-6 coming down the pike, i'd rather just go with relying on
> > the new warning flag about misleading indentation.
> 
> GCC 6 only warns about misleading additional indentation, not missing
> indentation:

that's what we're talking about here though.  the "Apple GOTO bug" was:
	if (foo)
		goto error;
		goto error;
-mike

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-31 19:32               ` Stephen Gallagher
@ 2016-04-04 13:17                 ` Stephen Gallagher
  2016-04-04 19:25                 ` Mike Frysinger
  1 sibling, 0 replies; 33+ messages in thread
From: Stephen Gallagher @ 2016-04-04 13:17 UTC (permalink / raw)
  To: libc-alpha


[-- Attachment #1.1: Type: text/plain, Size: 128 bytes --]

On 03/31/2016 03:32 PM, Stephen Gallagher wrote:
> https://sourceware.org/glibc/wiki/Proposals/GroupMerging
> 

*bump*



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-03-31 19:32               ` Stephen Gallagher
  2016-04-04 13:17                 ` Stephen Gallagher
@ 2016-04-04 19:25                 ` Mike Frysinger
  2016-04-12 16:19                   ` Carlos O'Donell
  1 sibling, 1 reply; 33+ messages in thread
From: Mike Frysinger @ 2016-04-04 19:25 UTC (permalink / raw)
  To: Stephen Gallagher; +Cc: libc-alpha

[-- Attachment #1: Type: text/plain, Size: 1201 bytes --]

On 31 Mar 2016 15:32, Stephen Gallagher wrote:
> --- /dev/null
> +++ b/grp/grp-merge.c
>
> +#define BUFCHECK(size)		\
> +  do {						\
> +    if (c + size > buflen)	\
> +    {						\
> +        free (members);		\
> +        return ERANGE;		\
> +    }						\
> +  } while(0)

style is still broken here in multiple ways.  can't you find an plugin
for whatever editor you're using to automate this ?  it's getting tedious
to flag the same problems in every revision.
(1) trailing \ don't line up
(2) the { after the if is missing another level of indentation
(3) the free/return statements should be indented with tabs, not spaces
(4) the while(0) should have a space before the (
(5) the size field should have paren around it

> +     group member list. (This means walking back savedmemcount + 1 (char *) pointers

you still have too long lines in these files

> +When processing @samp{merge} for @samp{group} membership, the group GID
> +and name must be identical for both entries.  If only one or the other is
> +a match, the behavior is undefined.

as i asked previously, why can't we scope the undefined part a bit ?

i gave up scanning the rest of the patch considering the above issues.
-mike

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-04-04 19:25                 ` Mike Frysinger
@ 2016-04-12 16:19                   ` Carlos O'Donell
  2016-04-12 17:53                     ` Mike Frysinger
  0 siblings, 1 reply; 33+ messages in thread
From: Carlos O'Donell @ 2016-04-12 16:19 UTC (permalink / raw)
  To: Stephen Gallagher, libc-alpha, Mike Frysinger

On 04/04/2016 03:25 PM, Mike Frysinger wrote:

Mike,

Thanks for the review. Sorry for the frustration.

[snip formatting objections]

I'll fix the formatting before checking it in myself on behalf of Stephen.

I see only two places that needed cleanup:

Fixed BUFCHECK in grp/grp-merge.c
Fixed CHECK_MERGE in nss/getXXbyYY_r.c
Comment nit in grp/grp-merg.c s/NSS_STATUS NOTFOUND/NSS_STATUS_NOTFOUND/g

>> +When processing @samp{merge} for @samp{group} membership, the group GID
>> +and name must be identical for both entries.  If only one or the other is
>> +a match, the behavior is undefined.
> 
> as i asked previously, why can't we scope the undefined part a bit ?

Thanks for raising this issue again.

Lets hash it out.

It is undefined behaviour because that was easiest and gives the biggest
implementation-dependent flexibility.

If you have configured a system where a user has two different GIDs across
two merged services (same database) for a given group name, then you have
a misconfigured system. Depending on the availability of the services a
group name might change GID. This can cause serious problems. You might
get one GID one day, a different GID the other day. This can happen
*today* without the use of MERGE if you have two services and one becomes
temporarily inaccessible _and_ the services have conflicting numeric GID
for the same group name.

With the use of MERGE the problem is still the same, but now it impacts
the semantics of the merge operation (which always happens).

The proposed implementation ignores the second service that returns
a result with a non-matching GID or non-matching group name. Even though
the service returns a result it is treated as NSS_STATUS_NOTFOUND, and the
results are ignored as inconsistent with the results of the first service
that returned a result.

This results in a conservative set of results being returned. If the first
service is temporarily unavailable, the user will see their group GID change,
similar to the problem we have today with unavailable.

It seems wrong to return an error condition if the inconsistency is detected,
since this is not very "fail safe", and could leave you unable to use the
system at all.

We don't have enough usage data to say if this choice is what we need, and
so the suggestion to say "undefined behaviour." I hope that what is proposed
becomes acceptable over time, and it can eventually be documented.

Until then I'd like to see this remain undefined behaviour.

You can _detect_ this behaviour by using an iterator to find these conflicting
entries and then play with /etc/nsswitch.conf to find the service which has
them and eventually remove them.

Thoughts?

-- 
Cheers,
Carlos.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-04-12 16:19                   ` Carlos O'Donell
@ 2016-04-12 17:53                     ` Mike Frysinger
  2016-04-13 15:16                       ` Carlos O'Donell
  0 siblings, 1 reply; 33+ messages in thread
From: Mike Frysinger @ 2016-04-12 17:53 UTC (permalink / raw)
  To: Carlos O'Donell; +Cc: Stephen Gallagher, libc-alpha

[-- Attachment #1: Type: text/plain, Size: 3228 bytes --]

On 12 Apr 2016 12:19, Carlos O'Donell wrote:
> On 04/04/2016 03:25 PM, Mike Frysinger wrote:
> >> +When processing @samp{merge} for @samp{group} membership, the group GID
> >> +and name must be identical for both entries.  If only one or the other is
> >> +a match, the behavior is undefined.
> > 
> > as i asked previously, why can't we scope the undefined part a bit ?
> 
> Thanks for raising this issue again.
> 
> Lets hash it out.
> 
> It is undefined behaviour because that was easiest and gives the biggest
> implementation-dependent flexibility.
> 
> If you have configured a system where a user has two different GIDs across
> two merged services (same database) for a given group name, then you have
> a misconfigured system. Depending on the availability of the services a
> group name might change GID. This can cause serious problems. You might
> get one GID one day, a different GID the other day. This can happen
> *today* without the use of MERGE if you have two services and one becomes
> temporarily inaccessible _and_ the services have conflicting numeric GID
> for the same group name.
> 
> With the use of MERGE the problem is still the same, but now it impacts
> the semantics of the merge operation (which always happens).
> 
> The proposed implementation ignores the second service that returns
> a result with a non-matching GID or non-matching group name. Even though
> the service returns a result it is treated as NSS_STATUS_NOTFOUND, and the
> results are ignored as inconsistent with the results of the first service
> that returned a result.
> 
> This results in a conservative set of results being returned. If the first
> service is temporarily unavailable, the user will see their group GID change,
> similar to the problem we have today with unavailable.
> 
> It seems wrong to return an error condition if the inconsistency is detected,
> since this is not very "fail safe", and could leave you unable to use the
> system at all.
> 
> We don't have enough usage data to say if this choice is what we need, and
> so the suggestion to say "undefined behaviour." I hope that what is proposed
> becomes acceptable over time, and it can eventually be documented.
> 
> Until then I'd like to see this remain undefined behaviour.
> 
> You can _detect_ this behaviour by using an iterator to find these conflicting
> entries and then play with /etc/nsswitch.conf to find the service which has
> them and eventually remove them.

what i previously requested was:
	could you clarify "undefined" ?  people could interpret this as memory
	corruption / crashes, while others are are inconsistent results.  i think
	we just want the latter.
	...
	in general i'm fine with "undefined", i would just prefer scoping it a
	bit.  it's "undefined" in terms of the result set, but it's not in terms
	of your program eating itself.

so i don't think your desires are at odds with my request.  i simply want it
to say that the return values (or whatever) are undefined rather than having
the documentation imply all your programs will die an abort/assert crash, or
enter the "undefined behavior" space like we have with "C undefined behavior".
-mike

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-04-12 17:53                     ` Mike Frysinger
@ 2016-04-13 15:16                       ` Carlos O'Donell
  2016-04-13 15:32                         ` Mike Frysinger
  0 siblings, 1 reply; 33+ messages in thread
From: Carlos O'Donell @ 2016-04-13 15:16 UTC (permalink / raw)
  To: Stephen Gallagher, libc-alpha, Mike Frysinger

On 04/12/2016 01:53 PM, Mike Frysinger wrote:
>> You can _detect_ this behaviour by using an iterator to find these conflicting
>> entries and then play with /etc/nsswitch.conf to find the service which has
>> them and eventually remove them.
> 
> what i previously requested was:
> 	could you clarify "undefined" ?  people could interpret this as memory
> 	corruption / crashes, while others are are inconsistent results.  i think
> 	we just want the latter.
> 	...
> 	in general i'm fine with "undefined", i would just prefer scoping it a
> 	bit.  it's "undefined" in terms of the result set, but it's not in terms
> 	of your program eating itself.
> 
> so i don't think your desires are at odds with my request.  i simply want it
> to say that the return values (or whatever) are undefined rather than having
> the documentation imply all your programs will die an abort/assert crash, or
> enter the "undefined behavior" space like we have with "C undefined behavior".

I'm sorry, I missed that in your original response.

You raised a good point.

Let me rewrite it like this:

+When processing @samp{merge} for @samp{group} membership, the group GID
+and name must be identical for both entries.  If only one or the other is
+a match, the results are unspecified.

We use the C/C++/POSIX meaning of "unspecified behaviour" here.

That is to say that glibc imposes no requirements on what the results
should be if a misconfiguration is detected. However, the program is correct
and should not crash.

If you're OK with that then I'll use that wording.

-- 
Cheers,
Carlos.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-04-13 15:16                       ` Carlos O'Donell
@ 2016-04-13 15:32                         ` Mike Frysinger
  2016-04-15  5:02                           ` Carlos O'Donell
  0 siblings, 1 reply; 33+ messages in thread
From: Mike Frysinger @ 2016-04-13 15:32 UTC (permalink / raw)
  To: Carlos O'Donell; +Cc: Stephen Gallagher, libc-alpha

[-- Attachment #1: Type: text/plain, Size: 1733 bytes --]

On 13 Apr 2016 11:16, Carlos O'Donell wrote:
> On 04/12/2016 01:53 PM, Mike Frysinger wrote:
> >> You can _detect_ this behaviour by using an iterator to find these conflicting
> >> entries and then play with /etc/nsswitch.conf to find the service which has
> >> them and eventually remove them.
> > 
> > what i previously requested was:
> > 	could you clarify "undefined" ?  people could interpret this as memory
> > 	corruption / crashes, while others are are inconsistent results.  i think
> > 	we just want the latter.
> > 	...
> > 	in general i'm fine with "undefined", i would just prefer scoping it a
> > 	bit.  it's "undefined" in terms of the result set, but it's not in terms
> > 	of your program eating itself.
> > 
> > so i don't think your desires are at odds with my request.  i simply want it
> > to say that the return values (or whatever) are undefined rather than having
> > the documentation imply all your programs will die an abort/assert crash, or
> > enter the "undefined behavior" space like we have with "C undefined behavior".
> 
> I'm sorry, I missed that in your original response.
> 
> You raised a good point.
> 
> Let me rewrite it like this:
> 
> +When processing @samp{merge} for @samp{group} membership, the group GID
> +and name must be identical for both entries.  If only one or the other is
> +a match, the results are unspecified.
> 
> We use the C/C++/POSIX meaning of "unspecified behaviour" here.
> 
> That is to say that glibc imposes no requirements on what the results
> should be if a misconfiguration is detected. However, the program is correct
> and should not crash.
> 
> If you're OK with that then I'll use that wording.

sgtm, thanks
-mike

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-04-13 15:32                         ` Mike Frysinger
@ 2016-04-15  5:02                           ` Carlos O'Donell
  2016-04-30  2:28                             ` Carlos O'Donell
  0 siblings, 1 reply; 33+ messages in thread
From: Carlos O'Donell @ 2016-04-15  5:02 UTC (permalink / raw)
  To: Stephen Gallagher, libc-alpha

On 04/13/2016 11:32 AM, Mike Frysinger wrote:
> On 13 Apr 2016 11:16, Carlos O'Donell wrote:
>> On 04/12/2016 01:53 PM, Mike Frysinger wrote:
>>>> You can _detect_ this behaviour by using an iterator to find these conflicting
>>>> entries and then play with /etc/nsswitch.conf to find the service which has
>>>> them and eventually remove them.
>>>
>>> what i previously requested was:
>>> 	could you clarify "undefined" ?  people could interpret this as memory
>>> 	corruption / crashes, while others are are inconsistent results.  i think
>>> 	we just want the latter.
>>> 	...
>>> 	in general i'm fine with "undefined", i would just prefer scoping it a
>>> 	bit.  it's "undefined" in terms of the result set, but it's not in terms
>>> 	of your program eating itself.
>>>
>>> so i don't think your desires are at odds with my request.  i simply want it
>>> to say that the return values (or whatever) are undefined rather than having
>>> the documentation imply all your programs will die an abort/assert crash, or
>>> enter the "undefined behavior" space like we have with "C undefined behavior".
>>
>> I'm sorry, I missed that in your original response.
>>
>> You raised a good point.
>>
>> Let me rewrite it like this:
>>
>> +When processing @samp{merge} for @samp{group} membership, the group GID
>> +and name must be identical for both entries.  If only one or the other is
>> +a match, the results are unspecified.
>>
>> We use the C/C++/POSIX meaning of "unspecified behaviour" here.
>>
>> That is to say that glibc imposes no requirements on what the results
>> should be if a misconfiguration is detected. However, the program is correct
>> and should not crash.
>>
>> If you're OK with that then I'll use that wording.
> 
> sgtm, thanks
> -mike
 
OK, so this is almost about ready to checkin.

The minor nit is that this patch creates new PLT entries in libc.so.6
and I need to determine if I can remove those with internal aliases
(since the nscd code should have recompiled copies and not need the
symbol).

-- 
Cheers,
Carlos.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH] nsswitch: Add group merging support
  2016-04-15  5:02                           ` Carlos O'Donell
@ 2016-04-30  2:28                             ` Carlos O'Donell
  0 siblings, 0 replies; 33+ messages in thread
From: Carlos O'Donell @ 2016-04-30  2:28 UTC (permalink / raw)
  To: Stephen Gallagher, libc-alpha

On 04/15/2016 01:02 AM, Carlos O'Donell wrote:
> OK, so this is almost about ready to checkin.
> 
> The minor nit is that this patch creates new PLT entries in libc.so.6
> and I need to determine if I can remove those with internal aliases
> (since the nscd code should have recompiled copies and not need the
> symbol).
> 

Stephen, Thank you very much for working on this project!

Checked in for 2.24.

Final version used include/grp-merge.h to remove the PLT entries from
libc.so.6, just standard operating procedure:
~~~
#ifndef _GRP_MERGE_H
#include <grp/grp-merge.h>

libc_hidden_proto (__copy_grp)
libc_hidden_proto (__merge_grp)

#endif /* _GRP_MERGE_H */
~~~
This also removes the need for `#include "grp-merge.h"` or other
forms of ""-based include (since you can now use the standard include
dirs to find the file.

commit ced8f8933673f4efda1d666d26a1a949602035ed
Author: Stephen Gallagher <sgallagh@redhat.com>
Date:   Fri Apr 29 22:11:09 2016 -0400

    NSS: Implement group merging support.
    
...

-- 
Cheers,
Carlos.

^ permalink raw reply	[flat|nested] 33+ messages in thread

end of thread, other threads:[~2016-04-30  2:28 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-12-16 15:11 [PATCH] nsswitch: Add group merging support Stephen Gallagher
2015-12-29 21:58 ` Mike Frysinger
2016-01-04 14:10   ` Stephen Gallagher
2016-01-04 14:12     ` Stephen Gallagher
2016-01-04 15:42       ` Joseph Myers
2016-01-04 17:40         ` Stephen Gallagher
2016-01-06 22:41           ` Andreas Schwab
2016-01-08  0:46             ` Stephen Gallagher
2016-01-13  1:31               ` Carlos O'Donell
2016-03-14 15:27                 ` Stephen Gallagher
2016-03-22  0:40       ` Mike Frysinger
2016-03-28 13:50         ` Stephen Gallagher
2016-03-28 13:51           ` Stephen Gallagher
2016-03-28 13:54             ` Stephen Gallagher
2016-03-28 13:55               ` Stephen Gallagher
2016-03-28 17:16                 ` Florian Weimer
2016-03-28 17:35                   ` Stephen Gallagher
2016-03-28 17:36                     ` Stephen Gallagher
2016-03-28 19:40           ` Mike Frysinger
2016-03-29  5:07             ` Florian Weimer
2016-03-31 19:32               ` Stephen Gallagher
2016-04-04 13:17                 ` Stephen Gallagher
2016-04-04 19:25                 ` Mike Frysinger
2016-04-12 16:19                   ` Carlos O'Donell
2016-04-12 17:53                     ` Mike Frysinger
2016-04-13 15:16                       ` Carlos O'Donell
2016-04-13 15:32                         ` Mike Frysinger
2016-04-15  5:02                           ` Carlos O'Donell
2016-04-30  2:28                             ` Carlos O'Donell
2016-04-01  2:00               ` Mike Frysinger
2016-01-13  1:24   ` Carlos O'Donell
2016-01-20 20:32   ` Carlos O'Donell
2016-03-28 13:14 ` Florian Weimer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).