/* invalidator.c - functions for invalidating external caches Copyright (C) 2013-2022 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 #include #include #include #include #include #include #include #include #include #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 */ #ifdef HAVE_CLOSEFROM closefrom(0); #else 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); #endif /* 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 shouldn'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)); }