Blob Blame History Raw
/*
   invalidator.c - functions for invalidating external caches

   Copyright (C) 2013-2014 Arthur de Jong

   This 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.

   This 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 this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301 USA
*/

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "common.h"
#include "log.h"

/* the write end of a pipe that is used to signal the child process
   to invalidate the cache */
static int signalfd = -1;

/* we have our own implementation because nscd could use different names */
static const char *map2name(enum ldap_map_selector map)
{
  switch (map)
  {
    case LM_ALIASES:   return "aliases";
    case LM_ETHERS:    return "ethers";
    case LM_GROUP:     return "group";
    case LM_HOSTS:     return "hosts";
    case LM_NETGROUP:  return "netgroup";
    case LM_NETWORKS:  return "networks";
    case LM_PASSWD:    return "passwd";
    case LM_PROTOCOLS: return "protocols";
    case LM_RPC:       return "rpc";
    case LM_SERVICES:  return "services";
    case LM_SHADOW:    return "shadow";
    case LM_NFSIDMAP:  return "nfsidmap";
    case LM_NONE:
    default:           return NULL;
  }
}

/* invalidate the specified database */
static void exec_invalidate(const char *db)
{
  pid_t cpid;
  int i, status;
  char *argv[4];
  char cmdline[80];
#ifdef HAVE_EXECVPE
  char *newenviron[] = { NULL };
#endif
  /* build command line */
  if (strcmp(db, "nfsidmap") == 0)
  {
    argv[0] = "nfsidmap";
    argv[1] = "-c";
    argv[2] = NULL;
  }
  else
  {
    argv[0] = "nscd";
    argv[1] = "-i";
    argv[2] = (char *)db;
    argv[3] = NULL;
  }
  if (mysnprintf(cmdline, 80, "%s %s%s%s", argv[0], argv[1],
                 argv[2] != NULL ? " " : "", argv[2] != NULL ? argv[2] : ""))
  {
    log_log(LOG_ERR, "exec_invalidate(): cmdline buffer too small");
    return;
  }
  log_log(LOG_DEBUG, "invalidator: %s", cmdline);
  /* do fork/exec */
  switch (cpid=fork())
  {
    case 0: /* we are the child */
      /* close all file descriptors */
      i = sysconf(_SC_OPEN_MAX) - 1;
      /* if the system does not have OPEN_MAX just close the first 32 and
         hope we have closed enough */
      if (i < 0)
        i = 32;
      for (; i >= 0; i--)
        close(i);
      /* execute command */
#ifdef HAVE_EXECVPE
      execvpe(argv[0], argv, newenviron);
#else
      execvp(argv[0], argv);
#endif
      /* if we are here there has been an error */
      /* we can't log since we don't have any useful file descriptors */
      _exit(EXIT_FAILURE);
      break;
    case -1: /* we are the parent, but have an error */
      log_log(LOG_ERR, "invalidator: fork() failed: %s", strerror(errno));
      break;
    default: /* we are the parent */
      /* wait for child exit */
      do
      {
        errno = 0;
        i = waitpid(cpid, &status, 0);
      }
      while ((i < 0) && (errno == EINTR));
      if (i < 0)
        log_log(LOG_ERR, "invalidator: waitpid(%d) failed: %s", (int)cpid, strerror(errno));
      else if (WIFEXITED(status))
      {
        i = WEXITSTATUS(status);
        if (i == 0)
          log_log(LOG_DEBUG, "invalidator: %s (pid %d) success",
                  cmdline, (int)cpid);
        else
          log_log(LOG_DEBUG, "invalidator: %s (pid %d) failed (%d)",
                  cmdline, (int)cpid, i);
      }
      else if (WIFSIGNALED(status))
      {
        i = WTERMSIG(status);
        log_log(LOG_ERR, "invalidator: %s (pid %d) killed by %s (%d)",
                cmdline, (int)cpid, signame(i), i);
      }
      else
        log_log(LOG_ERR, "invalidator: %s (pid %d) had unknown failure",
                cmdline, (int)cpid);
      break;
  }
}

/* main loop for the invalidator process */
static void handle_requests(int fd)
{
  int i;
  uint8_t c;
  const char *db;
  log_log(LOG_DEBUG, "invalidator: starting");
  /* set up environment */
  (void)chdir("/");
  putenv("PATH=/usr/sbin:/usr/bin:/sbin:/bin");
  /* handle incoming requests */
  while (1)
  {
    i = read(fd, &c, sizeof(uint8_t));
    if (i == 0)
    {
      log_log(LOG_ERR, "invalidator: EOF");
      _exit(EXIT_SUCCESS);
    }
    else if (i < 0)
    {
      if (errno == EINTR)
        log_log(LOG_DEBUG, "invalidator: read failed (ignored): %s",
                strerror(errno));
      else
      {
        log_log(LOG_ERR, "invalidator: read failed: %s", strerror(errno));
        _exit(EXIT_SUCCESS);
      }
    }
    else
    {
      db = map2name((enum ldap_map_selector)c);
      if (db == NULL)
        log_log(LOG_ERR, "invalidator: invalid db received");
      else
        exec_invalidate(db);
    }
  }
}

/* start a child process that holds onto the original privileges with the
   purpose of running external cache invalidation commands */
int invalidator_start(void)
{
  int pipefds[2];
  pid_t cpid;
  int i;
  /* set up a pipe for communication */
  if (pipe(pipefds) < 0)
  {
    log_log(LOG_ERR, "pipe() failed: %s", strerror(errno));
    return -1;
  }
  /* set O_NONBLOCK on the write end to ensure that a hanging invalidator
     process does not bring down the rest of the application */
  if ((i = fcntl(pipefds[1], F_GETFL, 0)) < 0)
  {
    log_log(LOG_ERR, "fctnl(F_GETFL) failed: %s", strerror(errno));
    close(pipefds[0]);
    close(pipefds[1]);
    return -1;
  }
  if (fcntl(pipefds[1], F_SETFL, i | O_NONBLOCK) < 0)
  {
    log_log(LOG_ERR, "fctnl(F_SETFL,O_NONBLOCK) failed: %s", strerror(errno));
    close(pipefds[0]);
    close(pipefds[1]);
    return -1;
  }
  /* fork a child to perfrom the invalidate commands */
  cpid = fork();
  if (cpid < 0)
  {
    log_log(LOG_ERR, "fork() failed: %s", strerror(errno));
    close(pipefds[0]);
    close(pipefds[1]);
    return -1;
  }
  if (cpid == 0)
  {
    /* we are the child: close the write end and handle requests */
    close(pipefds[1]);
    handle_requests(pipefds[0]);
    /* the handle function should't return */
    _exit(EXIT_FAILURE);
  }
  /* we are the parent: close the read end and save the write end */
  close(pipefds[0]);
  signalfd = pipefds[1];
  return 0;
}

/* signal invalidator to invalidate the selected external cache */
void invalidator_do(enum ldap_map_selector map)
{
  uint8_t c;
  int rc;
  if (signalfd < 0)
    return;
  /* LM_NONE is used to signal all maps condigured in reconnect_invalidate */
  if (map == LM_NONE)
  {
    for (map = 0; map < LM_NONE ; map++)
      if (nslcd_cfg->reconnect_invalidate[map])
        invalidator_do(map);
    return;
  }
  /* write a single byte which should be atomic and not fill the PIPE
     buffer too soon on most platforms
     (nslcd should already ignore SIGPIPE) */
  c = (uint8_t)map;
  rc = write(signalfd, &c, sizeof(uint8_t));
  if (rc <= 0)
    log_log(LOG_WARNING, "error signalling invalidator: %s",
            strerror(errno));
}