[v2,15/23] nscd: Add __nscd_generic_get for generic lookup

Message ID ab41313a347c99db3b9fe67225f75e3d7ca73d7d.1774037705.git.fweimer@redhat.com (mailing list archive)
State Failed CI
Headers
Series NSS, nscd updates (for group merging and more) |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 fail Test failed
linaro-tcwg-bot/tcwg_glibc_check--master-arm fail Test failed

Commit Message

Florian Weimer March 20, 2026, 8:42 p.m. UTC
  Implement passwd and group handling only at this point.
---
 nscd/nscd_helper.c | 318 +++++++++++++++++++++++++++++++++++++++++++++
 nss/nss_generic.h  |  16 +++
 2 files changed, 334 insertions(+)
  

Comments

Carlos O'Donell March 24, 2026, 9:11 p.m. UTC | #1
On 3/20/26 4:42 PM, Florian Weimer wrote:
> Implement passwd and group handling only at this point.

LGTM.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

> ---
>   nscd/nscd_helper.c | 318 +++++++++++++++++++++++++++++++++++++++++++++
>   nss/nss_generic.h  |  16 +++
>   2 files changed, 334 insertions(+)
> 
> diff --git a/nscd/nscd_helper.c b/nscd/nscd_helper.c
> index 88bfb463c2..b113403f16 100644
> --- a/nscd/nscd_helper.c
> +++ b/nscd/nscd_helper.c
> @@ -38,6 +38,9 @@
>   #include <nss.h>
>   #include <struct___timespec64.h>
>   #include <scratch_buffer.h>
> +#include <parse_buffer.h>
> +#include <nss/nss_generic.h>
> +#include <grp.h>
>   
>   #include "nscd-client.h"
>   #include "nscd-dbtype.h"
> @@ -802,3 +805,318 @@ __nscd_read_from_socket (const char *key, size_t keylen, request_type type,
>   
>     return ret;
>   }
> +
> +/* Used to implement __nscd_parse_and_copy.  */
> +static int __nscd_parse_and_copy_group (enum nss_lookup_type,
> +					void *, ssize_t, void **);
> +static int __nscd_parse_and_copy_passwd (enum nss_lookup_type,
> +					 void *, ssize_t, void **);
> +
> +/* Use __nss_generic_dup to copy NSS data according to LT at BUFFER
> +   (of LENGTH bytes) to *RESULT, or NULL if there is no data.  Return
> +   0 for success (including a negative result), or a negative error
> +   code: -ENOENT for no data available,, -EMSGSIZE for a parse
> +   failure, and -ENOMEM for a memory allocation error.  */
> +static int
> +__nscd_parse_and_copy (enum nss_lookup_type lt,
> +		       void *buffer, ssize_t length, void **result)
> +{
> +  switch (lt)
> +    {
> +    case nss_lookup_getgrnam:
> +    case nss_lookup_getgrgid:
> +      return __nscd_parse_and_copy_group (lt, buffer, length, result);
> +    case nss_lookup_getpwuid:
> +    case nss_lookup_getpwnam:
> +      return __nscd_parse_and_copy_passwd (lt, buffer, length, result);
> +    }
> +  __builtin_unreachable ();
> +}
> +
> +/* Use the nscd shared memory cache to retrieve data.  On success,
> +   return true and write a __nss_generic_dup-allocated NSS struct
> +   pointer to *RESULT (positive result), or write NULL to *RESULT
> +   (negative result).  On failurem return false.  Failure can mean:
> +   the data is not in the mapping, the mapping is being concurrently
> +   modified, or there was a resource shortage.  *BUFFER can be used
> +   for temporary storage (but not for data in the returned result).  */
> +static bool
> +__nscd_try_cache (unsigned int db, request_type type,
> +		  const char *key, size_t keylen, enum nss_lookup_type lt,
> +		  struct scratch_buffer *buffer, void **result)
> +{
> +  assert (db < lastdb);
> +  struct mapped_database *mapped = &__nscd_mapped_databases[db];
> +  bool found = false;
> +
> +  for (int retries = 0; ; ++retries)
> +    {
> +      __libc_rwlock_rdlock (mapped->lock);
> +
> +      /* Check if the mapping is usable.  If not, try to replace it.  */
> +      if (mapped->mapsize == 0
> +	  || (mapped->head->nscd_certainly_running == 0
> +	      && mapped->head->timestamp + MAPPING_TIMEOUT < time_now ())
> +	  || mapped->head->data_size > mapped->datasize)
> +	{
> +	  /* Mapping is not usable as-is.  Attempt to replace it.  */
> +	  __libc_rwlock_unlock (mapped->lock);
> +	  __nscd_get_mapping (db);
> +	  if (mapped->mapsize == 0)
> +	    /* Could not get mapping.  */
> +	    break;
> +	}
> +
> +      ssize_t ret = __nscd_read_from_cache (key, keylen, type, buffer,
> +					    mapped);
> +      if (ret > 0)
> +	{
> +	  ret = __nscd_parse_and_copy (lt, buffer->data, ret, result);
> +	  if (ret == 0)
> +	    {
> +	      /* We got the data (result can be NULL for negative).  */
> +	      found = true;
> +	      break;
> +	    }
> +	  else if (ret != -EMSGSIZE)
> +	    /* Some other error.  Give up.  */
> +	    break;
> +	  /* Else fall through and retry.  */
> +	}
> +      else if (ret != -EINPROGRESS && ret != -EMSGSIZE)
> +	/* An error that does not go away with retrying.  */
> +	break;
> +
> +      if (retries == 5)
> +	/* Stop retrying.   */
> +	break;
> +
> +      __libc_rwlock_unlock (mapped->lock);
> +    }
> +
> +  __libc_rwlock_unlock (mapped->lock);
> +  return found;
> +}
> +
> +/* Use the nscd shared memory cache to retrieve data.  On success,
> +   write a pointer to an NSS struct to *RESULT, allocated using
> +   __nss_generic_dup according to LT, or write NULL to *RESULT
> +   (negative result) and return true.  On failure, return false.
> +   Failure can mean failure to connect to nscd, or a parse error in
> +   the data returned from nscd.  */
> +static bool
> +__nscd_try_socket (request_type type, const char *key, size_t keylen,
> +		   enum nss_lookup_type lt, struct scratch_buffer *buffer,
> +		   void **result)
> +{
> +  ssize_t length = __nscd_read_from_socket (key, keylen, type, buffer);
> +  /* The length can be negative.  Rely on the check in the parse function.  */
> +  int ret = __nscd_parse_and_copy (lt, buffer->data, length, result);
> +  return ret == 0;
> +}
> +
> +/* Common tail of __nscd_parse_and_copy_* implementations.  Return 0
> +   on success, -ENOMEM on failure, and -EMSGSIZE on parse error.  */
> +static inline int
> +__nscd_parse_and_copy_tail (enum nss_lookup_type lt, struct parse_buffer pb,
> +			    const void *nss_struct, void **result)
> +{
> +  if (!parse_buffer_has_failed (&pb))
> +    {
> +      *result = __nss_generic_dup (lt, nss_struct);
> +      if (*result != NULL)
> +	return 0;
> +      else
> +	return -ENOMEM;
> +    }
> +  else
> +    /* Parse error.  */
> +    return -EMSGSIZE;
> +}
> +
> +static int
> +__nscd_parse_and_copy_group (enum nss_lookup_type lt,
> +			     void *buffer, ssize_t length, void **result)
> +{
> +  if (length < 0)
> +    {
> +      __nscd_defer_database (NSS_DBSIDX_group);
> +      return -ENOENT;
> +    }
> +
> +  struct parse_buffer pb = parse_buffer_create (buffer, length);
> +
> +  int32_t gr_found = parse_buffer_field (&pb, 0, gr_response_header, found);
> +  if (gr_found == 1)
> +    {
> +      uint32_t gr_mem_cnt
> +	= parse_buffer_field (&pb, 0, gr_response_header, gr_mem_cnt);
> +
> +      /* Paranoia check to avoid overflow.  */
> +      if (gr_mem_cnt >= INT_MAX / 4)
> +	return -EMSGSIZE;
> +
> +      /* Skip over the array with the group member name lengths.  */
> +      size_t pos = (sizeof (gr_response_header)
> +		    + gr_mem_cnt * sizeof (uint32_t));
> +      struct group grp;
> +      grp.gr_name = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, gr_response_header, gr_name_len));
> +      grp.gr_passwd = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, gr_response_header, gr_passwd_len));
> +      grp.gr_gid = parse_buffer_field (&pb, 0,
> +				       gr_response_header, gr_gid);
> +      grp.gr_mem = __libc_reallocarray (NULL, gr_mem_cnt + 1,
> +					sizeof (*grp.gr_mem));
> +      if (grp.gr_mem == NULL)
> +	return -ENOMEM;
> +
> +      /* Need to free grp.gr_mem before returning after this point.  */
> +      int ret = 0;
> +
> +      size_t lenpos = sizeof (gr_response_header);
> +      for (uint32_t i = 0; i < gr_mem_cnt; ++i)
> +	grp.gr_mem[i] = parse_buffer_sized_cstring_advance
> +	  (&pb, &pos, parse_buffer_u32_advance (&pb, &lenpos));
> +      grp.gr_mem[gr_mem_cnt] = NULL;
> +
> +      ret = __nscd_parse_and_copy_tail (lt, pb, &grp, result);
> +      free (grp.gr_mem);
> +      return ret;
> +    }
> +  else if (gr_found == -1)
> +    {
> +      /* The daemon does not cache this database.  */
> +      __nscd_defer_database (NSS_DBSIDX_group);
> +      return -ENOENT;
> +    }
> +  else if (parse_buffer_has_failed (&pb))
> +    return -EMSGSIZE;
> +  else
> +    {
> +      *result = NULL;
> +      return 0;
> +    }
> +}
> +
> +
> +/* Common tail of __nscd_generic_get.  */
> +static bool
> +__nscd_generic_get_tail (enum nss_lookup_type lt, unsigned int db,
> +			 request_type type,
> +			 const char *nscd_key, size_t nscd_keylen,
> +			 void **result)
> +{
> +  bool ok = true;
> +  struct scratch_buffer buffer;
> +  scratch_buffer_init (&buffer);
> +
> +  /* Try the mapping first.  */
> +  bool cache_hit = __nscd_try_cache (db, type, nscd_key, nscd_keylen,
> +				     lt, &buffer, result);
> +
> +  /* If the cache did not produce data, try the socket.  The socket
> +     may produce a successful negative result (*result == NULL).  */
> +  if (!cache_hit)
> +    ok = __nscd_try_socket (type, nscd_key, nscd_keylen, lt, &buffer,
> +			    result);
> +
> +  scratch_buffer_free (&buffer);
> +  return ok;
> +}
> +
> +#define NSCD_NUMBER_BUFFER_LENGTH 20
> +
> +static int
> +__nscd_fmt_num (char storage[static NSCD_NUMBER_BUFFER_LENGTH], int number)
> +{
> +  return __snprintf (storage, NSCD_NUMBER_BUFFER_LENGTH, "%d", number) + 1;
> +}
> +
> +bool
> +__nscd_generic_get (enum nss_lookup_type lt, const void *key, void **result)
> +{
> +  /* Storage for keys that need to be converted.  */
> +  char storage[20];
> +
> +  int8_t db = __nscd_database_for_lookup[lt];
> +  if (db < 0 || !__nscd_use_database (db))
> +    return false;
> +
> +  switch (lt)
> +    {
> +      case nss_lookup_getgrgid:
> +	return __nscd_generic_get_tail (lt, db, GETGRBYGID, storage,
> +					__nscd_fmt_num (storage,
> +							*(const gid_t *) key),
> +					result);
> +      case nss_lookup_getgrnam:
> +	return __nscd_generic_get_tail (lt, db, GETGRBYNAME,
> +					key, strlen (key) + 1,
> +					result);
> +      case nss_lookup_getpwnam:
> +	return __nscd_generic_get_tail (lt, db, GETPWBYNAME,
> +					key, strlen (key) + 1,
> +					result);
> +      case nss_lookup_getpwuid:
> +	return __nscd_generic_get_tail (lt, db, GETPWBYUID, storage,
> +					__nscd_fmt_num (storage,
> +							*(const uid_t *) key),
> +					result);
> +    }
> +  __builtin_unreachable ();
> +}
> +
> +static int
> +__nscd_parse_and_copy_passwd (enum nss_lookup_type lt,
> +			      void *buffer, ssize_t length, void **result)
> +{
> +  if (length < 0)
> +    {
> +      __nscd_defer_database (NSS_DBSIDX_passwd);
> +      return -ENOENT;
> +    }
> +
> +  struct parse_buffer pb = parse_buffer_create (buffer, length);
> +
> +  int32_t pw_found = parse_buffer_field (&pb, 0, pw_response_header, found);
> +  if (pw_found == 1)
> +    {
> +      size_t pos = sizeof (pw_response_header);
> +      struct passwd pwd;
> +      pwd.pw_name = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_name_len));
> +      pwd.pw_passwd = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_passwd_len));
> +      pwd.pw_uid = parse_buffer_field (&pb, 0, pw_response_header, pw_uid);
> +      pwd.pw_gid = parse_buffer_field (&pb, 0, pw_response_header, pw_gid);
> +      pwd.pw_gecos = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_gecos_len));
> +      pwd.pw_dir = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_dir_len));
> +      pwd.pw_shell = parse_buffer_sized_cstring_advance
> +	(&pb, &pos,
> +	 parse_buffer_field (&pb, 0, pw_response_header, pw_shell_len));
> +
> +      return __nscd_parse_and_copy_tail (lt, pb, &pwd, result);
> +    }
> +  else if (pw_found == -1)
> +    {
> +      /* The daemon does not cache this database.  */
> +      __nscd_defer_database (NSS_DBSIDX_passwd);
> +      return -ENOENT;
> +    }
> +  else if (parse_buffer_has_failed (&pb))
> +    return -EMSGSIZE;
> +  else
> +    {
> +      *result = NULL;
> +      return 0;
> +    }
> +}
> diff --git a/nss/nss_generic.h b/nss/nss_generic.h
> index fcddf8bf54..8a5b2cb896 100644
> --- a/nss/nss_generic.h
> +++ b/nss/nss_generic.h
> @@ -64,4 +64,20 @@ void *__nss_generic_dup (enum nss_lookup_type lt, const void *source)
>      each lookup type, or -1 if the lookup has no corresponding database.  */
>   extern int8_t __nscd_database_for_lookup[nss_lookup_MAX] attribute_hidden;
>   
> +/* Interpreted according to enum nss_lookup_type.  Typically a string
> +   or a pointer to an integer.  */
> +typedef const void *nss_lookup_key;
> +
> +/* Query the nscd daemon for KEY according to LT.  On success, return
> +   true, and write a NSS struct pointer allocated using
> +   __nss_generic_dup to *RESULT (positive result), or a null pointer
> +   (negative result).  On failure, return false.  The caller is
> +   expected to attempt the NSS operation directly after a failure.
> +
> +   Failure is also possible if nscd lookup has been deferred; the
> +   implementation calls __nscd_use_database as a first step to check
> +   if nscd should actually be used.  */
> +bool __nscd_generic_get (enum nss_lookup_type lt, nss_lookup_key key,
> +                         void **result);
> +
>   #endif /* NSS_GENERIC_H */
  

Patch

diff --git a/nscd/nscd_helper.c b/nscd/nscd_helper.c
index 88bfb463c2..b113403f16 100644
--- a/nscd/nscd_helper.c
+++ b/nscd/nscd_helper.c
@@ -38,6 +38,9 @@ 
 #include <nss.h>
 #include <struct___timespec64.h>
 #include <scratch_buffer.h>
+#include <parse_buffer.h>
+#include <nss/nss_generic.h>
+#include <grp.h>
 
 #include "nscd-client.h"
 #include "nscd-dbtype.h"
@@ -802,3 +805,318 @@  __nscd_read_from_socket (const char *key, size_t keylen, request_type type,
 
   return ret;
 }
+
+/* Used to implement __nscd_parse_and_copy.  */
+static int __nscd_parse_and_copy_group (enum nss_lookup_type,
+					void *, ssize_t, void **);
+static int __nscd_parse_and_copy_passwd (enum nss_lookup_type,
+					 void *, ssize_t, void **);
+
+/* Use __nss_generic_dup to copy NSS data according to LT at BUFFER
+   (of LENGTH bytes) to *RESULT, or NULL if there is no data.  Return
+   0 for success (including a negative result), or a negative error
+   code: -ENOENT for no data available,, -EMSGSIZE for a parse
+   failure, and -ENOMEM for a memory allocation error.  */
+static int
+__nscd_parse_and_copy (enum nss_lookup_type lt,
+		       void *buffer, ssize_t length, void **result)
+{
+  switch (lt)
+    {
+    case nss_lookup_getgrnam:
+    case nss_lookup_getgrgid:
+      return __nscd_parse_and_copy_group (lt, buffer, length, result);
+    case nss_lookup_getpwuid:
+    case nss_lookup_getpwnam:
+      return __nscd_parse_and_copy_passwd (lt, buffer, length, result);
+    }
+  __builtin_unreachable ();
+}
+
+/* Use the nscd shared memory cache to retrieve data.  On success,
+   return true and write a __nss_generic_dup-allocated NSS struct
+   pointer to *RESULT (positive result), or write NULL to *RESULT
+   (negative result).  On failurem return false.  Failure can mean:
+   the data is not in the mapping, the mapping is being concurrently
+   modified, or there was a resource shortage.  *BUFFER can be used
+   for temporary storage (but not for data in the returned result).  */
+static bool
+__nscd_try_cache (unsigned int db, request_type type,
+		  const char *key, size_t keylen, enum nss_lookup_type lt,
+		  struct scratch_buffer *buffer, void **result)
+{
+  assert (db < lastdb);
+  struct mapped_database *mapped = &__nscd_mapped_databases[db];
+  bool found = false;
+
+  for (int retries = 0; ; ++retries)
+    {
+      __libc_rwlock_rdlock (mapped->lock);
+
+      /* Check if the mapping is usable.  If not, try to replace it.  */
+      if (mapped->mapsize == 0
+	  || (mapped->head->nscd_certainly_running == 0
+	      && mapped->head->timestamp + MAPPING_TIMEOUT < time_now ())
+	  || mapped->head->data_size > mapped->datasize)
+	{
+	  /* Mapping is not usable as-is.  Attempt to replace it.  */
+	  __libc_rwlock_unlock (mapped->lock);
+	  __nscd_get_mapping (db);
+	  if (mapped->mapsize == 0)
+	    /* Could not get mapping.  */
+	    break;
+	}
+
+      ssize_t ret = __nscd_read_from_cache (key, keylen, type, buffer,
+					    mapped);
+      if (ret > 0)
+	{
+	  ret = __nscd_parse_and_copy (lt, buffer->data, ret, result);
+	  if (ret == 0)
+	    {
+	      /* We got the data (result can be NULL for negative).  */
+	      found = true;
+	      break;
+	    }
+	  else if (ret != -EMSGSIZE)
+	    /* Some other error.  Give up.  */
+	    break;
+	  /* Else fall through and retry.  */
+	}
+      else if (ret != -EINPROGRESS && ret != -EMSGSIZE)
+	/* An error that does not go away with retrying.  */
+	break;
+
+      if (retries == 5)
+	/* Stop retrying.   */
+	break;
+
+      __libc_rwlock_unlock (mapped->lock);
+    }
+
+  __libc_rwlock_unlock (mapped->lock);
+  return found;
+}
+
+/* Use the nscd shared memory cache to retrieve data.  On success,
+   write a pointer to an NSS struct to *RESULT, allocated using
+   __nss_generic_dup according to LT, or write NULL to *RESULT
+   (negative result) and return true.  On failure, return false.
+   Failure can mean failure to connect to nscd, or a parse error in
+   the data returned from nscd.  */
+static bool
+__nscd_try_socket (request_type type, const char *key, size_t keylen,
+		   enum nss_lookup_type lt, struct scratch_buffer *buffer,
+		   void **result)
+{
+  ssize_t length = __nscd_read_from_socket (key, keylen, type, buffer);
+  /* The length can be negative.  Rely on the check in the parse function.  */
+  int ret = __nscd_parse_and_copy (lt, buffer->data, length, result);
+  return ret == 0;
+}
+
+/* Common tail of __nscd_parse_and_copy_* implementations.  Return 0
+   on success, -ENOMEM on failure, and -EMSGSIZE on parse error.  */
+static inline int
+__nscd_parse_and_copy_tail (enum nss_lookup_type lt, struct parse_buffer pb,
+			    const void *nss_struct, void **result)
+{
+  if (!parse_buffer_has_failed (&pb))
+    {
+      *result = __nss_generic_dup (lt, nss_struct);
+      if (*result != NULL)
+	return 0;
+      else
+	return -ENOMEM;
+    }
+  else
+    /* Parse error.  */
+    return -EMSGSIZE;
+}
+
+static int
+__nscd_parse_and_copy_group (enum nss_lookup_type lt,
+			     void *buffer, ssize_t length, void **result)
+{
+  if (length < 0)
+    {
+      __nscd_defer_database (NSS_DBSIDX_group);
+      return -ENOENT;
+    }
+
+  struct parse_buffer pb = parse_buffer_create (buffer, length);
+
+  int32_t gr_found = parse_buffer_field (&pb, 0, gr_response_header, found);
+  if (gr_found == 1)
+    {
+      uint32_t gr_mem_cnt
+	= parse_buffer_field (&pb, 0, gr_response_header, gr_mem_cnt);
+
+      /* Paranoia check to avoid overflow.  */
+      if (gr_mem_cnt >= INT_MAX / 4)
+	return -EMSGSIZE;
+
+      /* Skip over the array with the group member name lengths.  */
+      size_t pos = (sizeof (gr_response_header)
+		    + gr_mem_cnt * sizeof (uint32_t));
+      struct group grp;
+      grp.gr_name = parse_buffer_sized_cstring_advance
+	(&pb, &pos,
+	 parse_buffer_field (&pb, 0, gr_response_header, gr_name_len));
+      grp.gr_passwd = parse_buffer_sized_cstring_advance
+	(&pb, &pos,
+	 parse_buffer_field (&pb, 0, gr_response_header, gr_passwd_len));
+      grp.gr_gid = parse_buffer_field (&pb, 0,
+				       gr_response_header, gr_gid);
+      grp.gr_mem = __libc_reallocarray (NULL, gr_mem_cnt + 1,
+					sizeof (*grp.gr_mem));
+      if (grp.gr_mem == NULL)
+	return -ENOMEM;
+
+      /* Need to free grp.gr_mem before returning after this point.  */
+      int ret = 0;
+
+      size_t lenpos = sizeof (gr_response_header);
+      for (uint32_t i = 0; i < gr_mem_cnt; ++i)
+	grp.gr_mem[i] = parse_buffer_sized_cstring_advance
+	  (&pb, &pos, parse_buffer_u32_advance (&pb, &lenpos));
+      grp.gr_mem[gr_mem_cnt] = NULL;
+
+      ret = __nscd_parse_and_copy_tail (lt, pb, &grp, result);
+      free (grp.gr_mem);
+      return ret;
+    }
+  else if (gr_found == -1)
+    {
+      /* The daemon does not cache this database.  */
+      __nscd_defer_database (NSS_DBSIDX_group);
+      return -ENOENT;
+    }
+  else if (parse_buffer_has_failed (&pb))
+    return -EMSGSIZE;
+  else
+    {
+      *result = NULL;
+      return 0;
+    }
+}
+
+
+/* Common tail of __nscd_generic_get.  */
+static bool
+__nscd_generic_get_tail (enum nss_lookup_type lt, unsigned int db,
+			 request_type type,
+			 const char *nscd_key, size_t nscd_keylen,
+			 void **result)
+{
+  bool ok = true;
+  struct scratch_buffer buffer;
+  scratch_buffer_init (&buffer);
+
+  /* Try the mapping first.  */
+  bool cache_hit = __nscd_try_cache (db, type, nscd_key, nscd_keylen,
+				     lt, &buffer, result);
+
+  /* If the cache did not produce data, try the socket.  The socket
+     may produce a successful negative result (*result == NULL).  */
+  if (!cache_hit)
+    ok = __nscd_try_socket (type, nscd_key, nscd_keylen, lt, &buffer,
+			    result);
+
+  scratch_buffer_free (&buffer);
+  return ok;
+}
+
+#define NSCD_NUMBER_BUFFER_LENGTH 20
+
+static int
+__nscd_fmt_num (char storage[static NSCD_NUMBER_BUFFER_LENGTH], int number)
+{
+  return __snprintf (storage, NSCD_NUMBER_BUFFER_LENGTH, "%d", number) + 1;
+}
+
+bool
+__nscd_generic_get (enum nss_lookup_type lt, const void *key, void **result)
+{
+  /* Storage for keys that need to be converted.  */
+  char storage[20];
+
+  int8_t db = __nscd_database_for_lookup[lt];
+  if (db < 0 || !__nscd_use_database (db))
+    return false;
+
+  switch (lt)
+    {
+      case nss_lookup_getgrgid:
+	return __nscd_generic_get_tail (lt, db, GETGRBYGID, storage,
+					__nscd_fmt_num (storage,
+							*(const gid_t *) key),
+					result);
+      case nss_lookup_getgrnam:
+	return __nscd_generic_get_tail (lt, db, GETGRBYNAME,
+					key, strlen (key) + 1,
+					result);
+      case nss_lookup_getpwnam:
+	return __nscd_generic_get_tail (lt, db, GETPWBYNAME,
+					key, strlen (key) + 1,
+					result);
+      case nss_lookup_getpwuid:
+	return __nscd_generic_get_tail (lt, db, GETPWBYUID, storage,
+					__nscd_fmt_num (storage,
+							*(const uid_t *) key),
+					result);
+    }
+  __builtin_unreachable ();
+}
+
+static int
+__nscd_parse_and_copy_passwd (enum nss_lookup_type lt,
+			      void *buffer, ssize_t length, void **result)
+{
+  if (length < 0)
+    {
+      __nscd_defer_database (NSS_DBSIDX_passwd);
+      return -ENOENT;
+    }
+
+  struct parse_buffer pb = parse_buffer_create (buffer, length);
+
+  int32_t pw_found = parse_buffer_field (&pb, 0, pw_response_header, found);
+  if (pw_found == 1)
+    {
+      size_t pos = sizeof (pw_response_header);
+      struct passwd pwd;
+      pwd.pw_name = parse_buffer_sized_cstring_advance
+	(&pb, &pos,
+	 parse_buffer_field (&pb, 0, pw_response_header, pw_name_len));
+      pwd.pw_passwd = parse_buffer_sized_cstring_advance
+	(&pb, &pos,
+	 parse_buffer_field (&pb, 0, pw_response_header, pw_passwd_len));
+      pwd.pw_uid = parse_buffer_field (&pb, 0, pw_response_header, pw_uid);
+      pwd.pw_gid = parse_buffer_field (&pb, 0, pw_response_header, pw_gid);
+      pwd.pw_gecos = parse_buffer_sized_cstring_advance
+	(&pb, &pos,
+	 parse_buffer_field (&pb, 0, pw_response_header, pw_gecos_len));
+      pwd.pw_dir = parse_buffer_sized_cstring_advance
+	(&pb, &pos,
+	 parse_buffer_field (&pb, 0, pw_response_header, pw_dir_len));
+      pwd.pw_shell = parse_buffer_sized_cstring_advance
+	(&pb, &pos,
+	 parse_buffer_field (&pb, 0, pw_response_header, pw_shell_len));
+
+      return __nscd_parse_and_copy_tail (lt, pb, &pwd, result);
+    }
+  else if (pw_found == -1)
+    {
+      /* The daemon does not cache this database.  */
+      __nscd_defer_database (NSS_DBSIDX_passwd);
+      return -ENOENT;
+    }
+  else if (parse_buffer_has_failed (&pb))
+    return -EMSGSIZE;
+  else
+    {
+      *result = NULL;
+      return 0;
+    }
+}
diff --git a/nss/nss_generic.h b/nss/nss_generic.h
index fcddf8bf54..8a5b2cb896 100644
--- a/nss/nss_generic.h
+++ b/nss/nss_generic.h
@@ -64,4 +64,20 @@  void *__nss_generic_dup (enum nss_lookup_type lt, const void *source)
    each lookup type, or -1 if the lookup has no corresponding database.  */
 extern int8_t __nscd_database_for_lookup[nss_lookup_MAX] attribute_hidden;
 
+/* Interpreted according to enum nss_lookup_type.  Typically a string
+   or a pointer to an integer.  */
+typedef const void *nss_lookup_key;
+
+/* Query the nscd daemon for KEY according to LT.  On success, return
+   true, and write a NSS struct pointer allocated using
+   __nss_generic_dup to *RESULT (positive result), or a null pointer
+   (negative result).  On failure, return false.  The caller is
+   expected to attempt the NSS operation directly after a failure.
+
+   Failure is also possible if nscd lookup has been deferred; the
+   implementation calls __nscd_use_database as a first step to check
+   if nscd should actually be used.  */
+bool __nscd_generic_get (enum nss_lookup_type lt, nss_lookup_key key,
+                         void **result);
+
 #endif /* NSS_GENERIC_H */