Blame nscd/initgrcache.c

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