Blame nscd/initgrcache.c

Packit 6c4009
/* Cache handling for host lookup.
Packit 6c4009
   Copyright (C) 2004-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
   Contributed by Ulrich Drepper <drepper@redhat.com>, 2004.
Packit 6c4009
Packit 6c4009
   This program is free software; you can redistribute it and/or modify
Packit 6c4009
   it under the terms of the GNU General Public License as published
Packit 6c4009
   by the Free Software Foundation; version 2 of the License, or
Packit 6c4009
   (at your option) any later version.
Packit 6c4009
Packit 6c4009
   This program is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 6c4009
   GNU General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU General Public License
Packit 6c4009
   along with this program; if not, see <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <assert.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <grp.h>
Packit 6c4009
#include <libintl.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <time.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
#include <sys/mman.h>
Packit 6c4009
#include <scratch_buffer.h>
Packit 6c4009
#include <config.h>
Packit 6c4009
Packit 6c4009
#include "dbg_log.h"
Packit 6c4009
#include "nscd.h"
Packit 6c4009
Packit 6c4009
#include "../nss/nsswitch.h"
Packit 6c4009
Packit 6c4009
#ifdef LINK_OBSOLETE_NSL
Packit 6c4009
# define DEFAULT_CONFIG "compat [NOTFOUND=return] files"
Packit 6c4009
#else
Packit 6c4009
# define DEFAULT_CONFIG "files"
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
/* Type of the lookup function.  */
Packit 6c4009
typedef enum nss_status (*initgroups_dyn_function) (const char *, gid_t,
Packit 6c4009
						    long int *, long int *,
Packit 6c4009
						    gid_t **, long int, int *);
Packit 6c4009
Packit 6c4009
Packit 6c4009
static const initgr_response_header notfound =
Packit 6c4009
{
Packit 6c4009
  .version = NSCD_VERSION,
Packit 6c4009
  .found = 0,
Packit 6c4009
  .ngrps = 0
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
Packit 6c4009
#include "../grp/compat-initgroups.c"
Packit 6c4009
Packit 6c4009
Packit 6c4009
static time_t
Packit 6c4009
addinitgroupsX (struct database_dyn *db, int fd, request_header *req,
Packit 6c4009
		void *key, uid_t uid, struct hashentry *const he,
Packit 6c4009
		struct datahead *dh)
Packit 6c4009
{
Packit 6c4009
  /* Search for the entry matching the key.  Please note that we don't
Packit 6c4009
     look again in the table whether the dataset is now available.  We
Packit 6c4009
     simply insert it.  It does not matter if it is in there twice.  The
Packit 6c4009
     pruning function only will look at the timestamp.  */
Packit 6c4009
Packit 6c4009
Packit 6c4009
  /* We allocate all data in one memory block: the iov vector,
Packit 6c4009
     the response header and the dataset itself.  */
Packit 6c4009
  struct dataset
Packit 6c4009
  {
Packit 6c4009
    struct datahead head;
Packit 6c4009
    initgr_response_header resp;
Packit 6c4009
    char strdata[0];
Packit 6c4009
  } *dataset = NULL;
Packit 6c4009
Packit 6c4009
  if (__glibc_unlikely (debug_level > 0))
Packit 6c4009
    {
Packit 6c4009
      if (he == NULL)
Packit 6c4009
	dbg_log (_("Haven't found \"%s\" in group cache!"), (char *) key);
Packit 6c4009
      else
Packit 6c4009
	dbg_log (_("Reloading \"%s\" in group cache!"), (char *) key);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  static service_user *group_database;
Packit 6c4009
  service_user *nip;
Packit 6c4009
  int no_more;
Packit 6c4009
Packit 6c4009
  if (group_database == NULL)
Packit Service 8fc214
    no_more = __nss_database_lookup2 ("group", NULL, DEFAULT_CONFIG,
Packit Service 8fc214
				      &group_database);
Packit 6c4009
  else
Packit 6c4009
    no_more = 0;
Packit 6c4009
  nip = group_database;
Packit 6c4009
Packit 6c4009
 /* We always use sysconf even if NGROUPS_MAX is defined.  That way, the
Packit 6c4009
     limit can be raised in the kernel configuration without having to
Packit 6c4009
     recompile libc.  */
Packit 6c4009
  long int limit = __sysconf (_SC_NGROUPS_MAX);
Packit 6c4009
Packit 6c4009
  long int size;
Packit 6c4009
  if (limit > 0)
Packit 6c4009
    /* We limit the size of the intially allocated array.  */
Packit 6c4009
    size = MIN (limit, 64);
Packit 6c4009
  else
Packit 6c4009
    /* No fixed limit on groups.  Pick a starting buffer size.  */
Packit 6c4009
    size = 16;
Packit 6c4009
Packit 6c4009
  long int start = 0;
Packit 6c4009
  bool all_tryagain = true;
Packit 6c4009
  bool any_success = false;
Packit 6c4009
Packit 6c4009
  /* This is temporary memory, we need not (and must not) call
Packit 6c4009
     mempool_alloc.  */
Packit 6c4009
  // XXX This really should use alloca.  need to change the backends.
Packit 6c4009
  gid_t *groups = (gid_t *) malloc (size * sizeof (gid_t));
Packit 6c4009
  if (__glibc_unlikely (groups == NULL))
Packit 6c4009
    /* No more memory.  */
Packit 6c4009
    goto out;
Packit 6c4009
Packit 6c4009
  /* Nothing added yet.  */
Packit 6c4009
  while (! no_more)
Packit 6c4009
    {
Packit 6c4009
      long int prev_start = start;
Packit 6c4009
      enum nss_status status;
Packit 6c4009
      initgroups_dyn_function fct;
Packit 6c4009
      fct = __nss_lookup_function (nip, "initgroups_dyn");
Packit 6c4009
Packit 6c4009
      if (fct == NULL)
Packit 6c4009
	{
Packit 6c4009
	  status = compat_call (nip, key, -1, &start, &size, &groups,
Packit 6c4009
				limit, &errno);
Packit 6c4009
Packit 6c4009
	  if (nss_next_action (nip, NSS_STATUS_UNAVAIL) != NSS_ACTION_CONTINUE)
Packit 6c4009
	    break;
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	status = DL_CALL_FCT (fct, (key, -1, &start, &size, &groups,
Packit 6c4009
				    limit, &errno));
Packit 6c4009
Packit 6c4009
      /* Remove duplicates.  */
Packit 6c4009
      long int cnt = prev_start;
Packit 6c4009
      while (cnt < start)
Packit 6c4009
	{
Packit 6c4009
	  long int inner;
Packit 6c4009
	  for (inner = 0; inner < prev_start; ++inner)
Packit 6c4009
	    if (groups[inner] == groups[cnt])
Packit 6c4009
	      break;
Packit 6c4009
Packit 6c4009
	  if (inner < prev_start)
Packit 6c4009
	    groups[cnt] = groups[--start];
Packit 6c4009
	  else
Packit 6c4009
	    ++cnt;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      if (status != NSS_STATUS_TRYAGAIN)
Packit 6c4009
	all_tryagain = false;
Packit 6c4009
Packit 6c4009
      /* This is really only for debugging.  */
Packit 6c4009
      if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN)
Packit Service 5f5c56
	__libc_fatal ("Illegal status in internal_getgrouplist.\n");
Packit 6c4009
Packit 6c4009
      any_success |= status == NSS_STATUS_SUCCESS;
Packit 6c4009
Packit 6c4009
      if (status != NSS_STATUS_SUCCESS
Packit 6c4009
	  && nss_next_action (nip, status) == NSS_ACTION_RETURN)
Packit 6c4009
	 break;
Packit 6c4009
Packit 6c4009
      if (nip->next == NULL)
Packit 6c4009
	no_more = -1;
Packit 6c4009
      else
Packit 6c4009
	nip = nip->next;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  bool all_written;
Packit 6c4009
  ssize_t total;
Packit 6c4009
  time_t timeout;
Packit 6c4009
 out:
Packit 6c4009
  all_written = true;
Packit 6c4009
  timeout = MAX_TIMEOUT_VALUE;
Packit 6c4009
  if (!any_success)
Packit 6c4009
    {
Packit 6c4009
      /* Nothing found.  Create a negative result record.  */
Packit 6c4009
      total = sizeof (notfound);
Packit 6c4009
Packit 6c4009
      if (he != NULL && all_tryagain)
Packit 6c4009
	{
Packit 6c4009
	  /* If we have an old record available but cannot find one now
Packit 6c4009
	     because the service is not available we keep the old record
Packit 6c4009
	     and make sure it does not get removed.  */
Packit 6c4009
	  if (reload_count != UINT_MAX && dh->nreloads == reload_count)
Packit 6c4009
	    /* Do not reset the value if we never not reload the record.  */
Packit 6c4009
	    dh->nreloads = reload_count - 1;
Packit 6c4009
Packit 6c4009
	  /* Reload with the same time-to-live value.  */
Packit 6c4009
	  timeout = dh->timeout = time (NULL) + db->postimeout;
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  /* We have no data.  This means we send the standard reply for this
Packit 6c4009
	     case.  */
Packit 6c4009
	  if (fd != -1
Packit 6c4009
	      && TEMP_FAILURE_RETRY (send (fd, &notfound, total,
Packit 6c4009
					   MSG_NOSIGNAL)) != total)
Packit 6c4009
	    all_written = false;
Packit 6c4009
Packit 6c4009
	  /* If we have a transient error or cannot permanently store
Packit 6c4009
	     the result, so be it.  */
Packit 6c4009
	  if (all_tryagain || __builtin_expect (db->negtimeout == 0, 0))
Packit 6c4009
	    {
Packit 6c4009
	      /* Mark the old entry as obsolete.  */
Packit 6c4009
	      if (dh != NULL)
Packit 6c4009
		dh->usable = false;
Packit 6c4009
	    }
Packit 6c4009
	  else if ((dataset = mempool_alloc (db, (sizeof (struct dataset)
Packit 6c4009
						  + req->key_len), 1)) != NULL)
Packit 6c4009
	    {
Packit 6c4009
	      timeout = datahead_init_neg (&dataset->head,
Packit 6c4009
					   (sizeof (struct dataset)
Packit 6c4009
					    + req->key_len), total,
Packit 6c4009
					   db->negtimeout);
Packit 6c4009
Packit 6c4009
	      /* This is the reply.  */
Packit 6c4009
	      memcpy (&dataset->resp, &notfound, total);
Packit 6c4009
Packit 6c4009
	      /* Copy the key data.  */
Packit 6c4009
	      char *key_copy = memcpy (dataset->strdata, key, req->key_len);
Packit 6c4009
Packit 6c4009
	      /* If necessary, we also propagate the data to disk.  */
Packit 6c4009
	      if (db->persistent)
Packit 6c4009
		{
Packit 6c4009
		  // XXX async OK?
Packit 6c4009
		  uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
Packit 6c4009
		  msync ((void *) pval,
Packit 6c4009
			 ((uintptr_t) dataset & pagesize_m1)
Packit 6c4009
			 + sizeof (struct dataset) + req->key_len, MS_ASYNC);
Packit 6c4009
		}
Packit 6c4009
Packit 6c4009
	      (void) cache_add (req->type, key_copy, req->key_len,
Packit 6c4009
				&dataset->head, true, db, uid, he == NULL);
Packit 6c4009
Packit 6c4009
	      pthread_rwlock_unlock (&db->lock);
Packit 6c4009
Packit 6c4009
	      /* Mark the old entry as obsolete.  */
Packit 6c4009
	      if (dh != NULL)
Packit 6c4009
		dh->usable = false;
Packit 6c4009
	    }
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
Packit 6c4009
      total = offsetof (struct dataset, strdata) + start * sizeof (int32_t);
Packit 6c4009
Packit 6c4009
      /* If we refill the cache, first assume the reconrd did not
Packit 6c4009
	 change.  Allocate memory on the cache since it is likely
Packit 6c4009
	 discarded anyway.  If it turns out to be necessary to have a
Packit 6c4009
	 new record we can still allocate real memory.  */
Packit 6c4009
      bool alloca_used = false;
Packit 6c4009
      dataset = NULL;
Packit 6c4009
Packit 6c4009
      if (he == NULL)
Packit 6c4009
	dataset = (struct dataset *) mempool_alloc (db, total + req->key_len,
Packit 6c4009
						    1);
Packit 6c4009
Packit 6c4009
      if (dataset == NULL)
Packit 6c4009
	{
Packit 6c4009
	  /* We cannot permanently add the result in the moment.  But
Packit 6c4009
	     we can provide the result as is.  Store the data in some
Packit 6c4009
	     temporary memory.  */
Packit 6c4009
	  dataset = (struct dataset *) alloca (total + req->key_len);
Packit 6c4009
Packit 6c4009
	  /* We cannot add this record to the permanent database.  */
Packit 6c4009
	  alloca_used = true;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      timeout = datahead_init_pos (&dataset->head, total + req->key_len,
Packit 6c4009
				   total - offsetof (struct dataset, resp),
Packit 6c4009
				   he == NULL ? 0 : dh->nreloads + 1,
Packit 6c4009
				   db->postimeout);
Packit 6c4009
Packit 6c4009
      dataset->resp.version = NSCD_VERSION;
Packit 6c4009
      dataset->resp.found = 1;
Packit 6c4009
      dataset->resp.ngrps = start;
Packit 6c4009
Packit 6c4009
      char *cp = dataset->strdata;
Packit 6c4009
Packit 6c4009
      /* Copy the GID values.  If the size of the types match this is
Packit 6c4009
	 very simple.  */
Packit 6c4009
      if (sizeof (gid_t) == sizeof (int32_t))
Packit 6c4009
	cp = mempcpy (cp, groups, start * sizeof (gid_t));
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  gid_t *gcp = (gid_t *) cp;
Packit 6c4009
Packit 6c4009
	  for (int i = 0; i < start; ++i)
Packit 6c4009
	    *gcp++ = groups[i];
Packit 6c4009
Packit 6c4009
	  cp = (char *) gcp;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      /* Finally the user name.  */
Packit 6c4009
      memcpy (cp, key, req->key_len);
Packit 6c4009
Packit 6c4009
      assert (cp == dataset->strdata + total - offsetof (struct dataset,
Packit 6c4009
							 strdata));
Packit 6c4009
Packit 6c4009
      /* Now we can determine whether on refill we have to create a new
Packit 6c4009
	 record or not.  */
Packit 6c4009
      if (he != NULL)
Packit 6c4009
	{
Packit 6c4009
	  assert (fd == -1);
Packit 6c4009
Packit 6c4009
	  if (total + req->key_len == dh->allocsize
Packit 6c4009
	      && total - offsetof (struct dataset, resp) == dh->recsize
Packit 6c4009
	      && memcmp (&dataset->resp, dh->data,
Packit 6c4009
			 dh->allocsize - offsetof (struct dataset, resp)) == 0)
Packit 6c4009
	    {
Packit 6c4009
	      /* The data has not changed.  We will just bump the
Packit 6c4009
		 timeout value.  Note that the new record has been
Packit 6c4009
		 allocated on the stack and need not be freed.  */
Packit 6c4009
	      dh->timeout = dataset->head.timeout;
Packit 6c4009
	      ++dh->nreloads;
Packit 6c4009
	    }
Packit 6c4009
	  else
Packit 6c4009
	    {
Packit 6c4009
	      /* We have to create a new record.  Just allocate
Packit 6c4009
		 appropriate memory and copy it.  */
Packit 6c4009
	      struct dataset *newp
Packit 6c4009
		= (struct dataset *) mempool_alloc (db, total + req->key_len,
Packit 6c4009
						    1);
Packit 6c4009
	      if (newp != NULL)
Packit 6c4009
		{
Packit 6c4009
		  /* Adjust pointer into the memory block.  */
Packit 6c4009
		  cp = (char *) newp + (cp - (char *) dataset);
Packit 6c4009
Packit 6c4009
		  dataset = memcpy (newp, dataset, total + req->key_len);
Packit 6c4009
		  alloca_used = false;
Packit 6c4009
		}
Packit 6c4009
Packit 6c4009
	      /* Mark the old record as obsolete.  */
Packit 6c4009
	      dh->usable = false;
Packit 6c4009
	    }
Packit 6c4009
	}
Packit 6c4009
      else
Packit 6c4009
	{
Packit 6c4009
	  /* We write the dataset before inserting it to the database
Packit 6c4009
	     since while inserting this thread might block and so would
Packit 6c4009
	     unnecessarily let the receiver wait.  */
Packit 6c4009
	  assert (fd != -1);
Packit 6c4009
Packit 6c4009
	  if (writeall (fd, &dataset->resp, dataset->head.recsize)
Packit 6c4009
	      != dataset->head.recsize)
Packit 6c4009
	    all_written = false;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
Packit 6c4009
      /* Add the record to the database.  But only if it has not been
Packit 6c4009
	 stored on the stack.  */
Packit 6c4009
      if (! alloca_used)
Packit 6c4009
	{
Packit 6c4009
	  /* If necessary, we also propagate the data to disk.  */
Packit 6c4009
	  if (db->persistent)
Packit 6c4009
	    {
Packit 6c4009
	      // XXX async OK?
Packit 6c4009
	      uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
Packit 6c4009
	      msync ((void *) pval,
Packit 6c4009
		     ((uintptr_t) dataset & pagesize_m1) + total +
Packit 6c4009
		     req->key_len, MS_ASYNC);
Packit 6c4009
	    }
Packit 6c4009
Packit 6c4009
	  (void) cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true,
Packit 6c4009
			    db, uid, he == NULL);
Packit 6c4009
Packit 6c4009
	  pthread_rwlock_unlock (&db->lock);
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  free (groups);
Packit 6c4009
Packit 6c4009
  if (__builtin_expect (!all_written, 0) && debug_level > 0)
Packit 6c4009
    {
Packit 6c4009
      char buf[256];
Packit 6c4009
      dbg_log (_("short write in %s: %s"), __FUNCTION__,
Packit 6c4009
	       strerror_r (errno, buf, sizeof (buf)));
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  return timeout;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key,
Packit 6c4009
	       uid_t uid)
Packit 6c4009
{
Packit 6c4009
  addinitgroupsX (db, fd, req, key, uid, NULL, NULL);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
Packit 6c4009
time_t
Packit 6c4009
readdinitgroups (struct database_dyn *db, struct hashentry *he,
Packit 6c4009
		 struct datahead *dh)
Packit 6c4009
{
Packit 6c4009
  request_header req =
Packit 6c4009
    {
Packit 6c4009
      .type = INITGROUPS,
Packit 6c4009
      .key_len = he->len
Packit 6c4009
    };
Packit 6c4009
Packit 6c4009
  return addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh);
Packit 6c4009
}