about summary refs log tree commit diff
path: root/nss
diff options
context:
space:
mode:
Diffstat (limited to 'nss')
-rw-r--r--nss/getXXbyYY_r.c110
-rw-r--r--nss/getnssent_r.c27
-rw-r--r--nss/nsswitch.c3
-rw-r--r--nss/nsswitch.h3
4 files changed, 137 insertions, 6 deletions
diff --git a/nss/getXXbyYY_r.c b/nss/getXXbyYY_r.c
index 113c687e06..93af2538ec 100644
--- a/nss/getXXbyYY_r.c
+++ b/nss/getXXbyYY_r.c
@@ -131,6 +131,52 @@
 # 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
@@ -152,13 +198,16 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
   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;
@@ -278,9 +327,66 @@ INTERNAL (REENTRANT_NAME) (ADD_PARAMS, LOOKUP_TYPE *resbuf, char *buffer,
 	  && 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:
diff --git a/nss/getnssent_r.c b/nss/getnssent_r.c
index 456907b018..f5092482ef 100644
--- a/nss/getnssent_r.c
+++ b/nss/getnssent_r.c
@@ -79,7 +79,18 @@ __nss_setent (const char *func_name, db_lookup_function lookup_fct,
       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;
     }
@@ -175,8 +186,18 @@ __nss_getent_r (const char *getent_func_name,
 
       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;
diff --git a/nss/nsswitch.c b/nss/nsswitch.c
index bb644cb373..d7706506f0 100644
--- a/nss/nsswitch.c
+++ b/nss/nsswitch.c
@@ -712,6 +712,9 @@ nss_parse_service_list (const char *line)
 	      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;
 
diff --git a/nss/nsswitch.h b/nss/nsswitch.h
index 0074ee1d65..54c8b656f7 100644
--- a/nss/nsswitch.h
+++ b/nss/nsswitch.h
@@ -32,7 +32,8 @@
 typedef enum
 {
   NSS_ACTION_CONTINUE,
-  NSS_ACTION_RETURN
+  NSS_ACTION_RETURN,
+  NSS_ACTION_MERGE
 } lookup_actions;