summaryrefslogtreecommitdiff
path: root/nscd
diff options
context:
space:
mode:
authorJakub Jelinek <jakub@redhat.com>2004-10-04 08:56:18 +0000
committerJakub Jelinek <jakub@redhat.com>2004-10-04 08:56:18 +0000
commit85148842d401edf64f9edee7e5819a947c289ed2 (patch)
tree42f228e998070f60c3bdb2018c9921b221a6851b /nscd
parent6d96590587deec027c04fe576f11cff0f445eb32 (diff)
Updated to fedora-glibc-20041004T0747cvs/fedora-glibc-2_3_3-64
Diffstat (limited to 'nscd')
-rw-r--r--nscd/Makefile50
-rw-r--r--nscd/connections.c702
-rw-r--r--nscd/dbg_log.c9
-rw-r--r--nscd/nscd.c17
-rw-r--r--nscd/nscd.conf8
-rw-r--r--nscd/nscd.h21
-rw-r--r--nscd/nscd_conf.c44
-rw-r--r--nscd/nscd_helper.c3
-rw-r--r--nscd/nscd_initgroups.c15
-rw-r--r--nscd/nscd_stat.c29
-rw-r--r--nscd/selinux.c4
11 files changed, 768 insertions, 134 deletions
diff --git a/nscd/Makefile b/nscd/Makefile
index d597cf77cb..b0ef3cd5f2 100644
--- a/nscd/Makefile
+++ b/nscd/Makefile
@@ -74,28 +74,28 @@ ifeq (yesyes,$(have-fpie)$(build-shared))
nscd-cflags += -fpie
endif
-CFLAGS-nscd.c = $(nscd-cflags)
-CFLAGS-connections.c = $(nscd-cflags)
-CFLAGS-pwdcache.c = $(nscd-cflags)
-CFLAGS-getpwnam_r.c = $(nscd-cflags)
-CFLAGS-getpwuid_r.c = $(nscd-cflags)
-CFLAGS-grpcache.c = $(nscd-cflags)
-CFLAGS-getgrnam_r.c = $(nscd-cflags)
-CFLAGS-getgrgid_r.c = $(nscd-cflags)
-CFLAGS-hstcache.c = $(nscd-cflags)
-CFLAGS-gethstbyad_r.c = $(nscd-cflags)
-CFLAGS-gethstbynm2_r.c = $(nscd-cflags)
-CFLAGS-dbg_log.c = $(nscd-cflags)
-CFLAGS-nscd_conf.c = $(nscd-cflags)
-CFLAGS-nscd_stat.c = $(nscd-cflags)
-CFLAGS-cache.c = $(nscd-cflags)
-CFLAGS-xmalloc.c = $(nscd-cflags)
-CFLAGS-xstrdup.c = $(nscd-cflags)
-CFLAGS-mem.c = $(nscd-cflags)
-CFLAGS-nscd_setup_thread.c = $(nscd-cflags)
-CFLAGS-aicache.c = $(nscd-cflags)
-CFLAGS-selinux.c = $(nscd-cflags)
-CFLAGS-initgrcache.c = $(nscd-cflags)
+CFLAGS-nscd.c += $(nscd-cflags)
+CFLAGS-connections.c += $(nscd-cflags)
+CFLAGS-pwdcache.c += $(nscd-cflags)
+CFLAGS-getpwnam_r.c += $(nscd-cflags)
+CFLAGS-getpwuid_r.c += $(nscd-cflags)
+CFLAGS-grpcache.c += $(nscd-cflags)
+CFLAGS-getgrnam_r.c += $(nscd-cflags)
+CFLAGS-getgrgid_r.c += $(nscd-cflags)
+CFLAGS-hstcache.c += $(nscd-cflags)
+CFLAGS-gethstbyad_r.c += $(nscd-cflags)
+CFLAGS-gethstbynm2_r.c += $(nscd-cflags)
+CFLAGS-dbg_log.c += $(nscd-cflags)
+CFLAGS-nscd_conf.c += $(nscd-cflags)
+CFLAGS-nscd_stat.c += $(nscd-cflags)
+CFLAGS-cache.c += $(nscd-cflags)
+CFLAGS-xmalloc.c += $(nscd-cflags)
+CFLAGS-xstrdup.c += $(nscd-cflags)
+CFLAGS-mem.c += $(nscd-cflags)
+CFLAGS-nscd_setup_thread.c += $(nscd-cflags)
+CFLAGS-aicache.c += $(nscd-cflags)
+CFLAGS-selinux.c += $(nscd-cflags)
+CFLAGS-initgrcache.c += $(nscd-cflags)
ifeq (yesyes,$(have-fpie)$(build-shared))
$(objpfx)nscd: $(addprefix $(objpfx),$(nscd-modules:=.o))
@@ -117,9 +117,11 @@ $(objpfx)nscd: $(nscd-modules:%=$(objpfx)%.o)
$(objpfx)nscd_nischeck: $(objpfx)nscd_nischeck.o
ifeq ($(build-shared),yes)
-$(objpfx)nscd: $(shared-thread-library) $(common-objpfx)nis/libnsl.so
+$(objpfx)nscd: $(common-objpfx)rt/librt.so $(shared-thread-library) \
+ $(common-objpfx)nis/libnsl.so
$(objpfx)nscd_nischeck: $(common-objpfx)nis/libnsl.so
else
-$(objpfx)nscd: $(static-thread-library) $(common-objpfx)nis/libnsl.a
+$(objpfx)nscd: $(common-objpfx)rt/librt.a $(static-thread-library) \
+ $(common-objpfx)nis/libnsl.a
$(objpfx)nscd_nischeck: $(common-objpfx)nis/libnsl.a
endif
diff --git a/nscd/connections.c b/nscd/connections.c
index 53795bb3b9..2bd3bec5b0 100644
--- a/nscd/connections.c
+++ b/nscd/connections.c
@@ -18,6 +18,7 @@
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */
+#include <alloca.h>
#include <assert.h>
#include <atomic.h>
#include <error.h>
@@ -32,6 +33,9 @@
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
+#ifdef HAVE_EPOLL
+# include <sys/epoll.h>
+#endif
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/poll.h>
@@ -65,6 +69,8 @@ static gid_t *server_groups;
#endif
static int server_ngroups;
+static pthread_attr_t attr;
+
static void begin_drop_privileges (void);
static void finish_drop_privileges (void);
@@ -163,8 +169,10 @@ static struct database_dyn *const serv2db[LASTREQ] =
#define CACHE_PRUNE_INTERVAL 15
-/* Number of threads to use. */
+/* Initial number of threads to use. */
int nthreads = -1;
+/* Maximum number of threads to use. */
+int max_nthreads = 32;
/* Socket for incoming connections. */
static int sock;
@@ -434,6 +442,18 @@ cannot create read-only descriptor for \"%s\"; no mmap"),
}
}
+ if (paranoia
+ && ((dbs[cnt].wr_fd != -1
+ && fcntl (dbs[cnt].wr_fd, F_SETFD, FD_CLOEXEC) == -1)
+ || (dbs[cnt].ro_fd != -1
+ && fcntl (dbs[cnt].ro_fd, F_SETFD, FD_CLOEXEC) == -1)))
+ {
+ dbg_log (_("\
+cannot set socket to close on exec: %s; disabling paranoia mode"),
+ strerror (errno));
+ paranoia = 0;
+ }
+
if (dbs[cnt].head == NULL)
{
/* We do not use the persistent database. Just
@@ -490,11 +510,22 @@ cannot create read-only descriptor for \"%s\"; no mmap"),
exit (1);
}
- /* We don't wait for data otherwise races between threads can get
- them stuck on accept. */
+ /* We don't want to get stuck on accept. */
int fl = fcntl (sock, F_GETFL);
- if (fl != -1)
- fcntl (sock, F_SETFL, fl | O_NONBLOCK);
+ if (fl == -1 || fcntl (sock, F_SETFL, fl | O_NONBLOCK) == -1)
+ {
+ dbg_log (_("cannot change socket to nonblocking mode: %s"),
+ strerror (errno));
+ exit (1);
+ }
+
+ /* The descriptor needs to be closed on exec. */
+ if (paranoia && fcntl (sock, F_SETFD, FD_CLOEXEC) == -1)
+ {
+ dbg_log (_("cannot set socket to close on exec: %s"),
+ strerror (errno));
+ exit (1);
+ }
/* Set permissions for the socket. */
chmod (_PATH_NSCDSOCKET, DEFFILEMODE);
@@ -785,91 +816,253 @@ cannot handle old request version %d; current version is %d"),
}
+/* Restart the process. */
+static void
+restart (void)
+{
+ /* First determine the parameters. We do not use the parameters
+ passed to main() since in case nscd is started by running the
+ dynamic linker this will not work. Yes, this is not the usual
+ case but nscd is part of glibc and we occasionally do this. */
+ size_t buflen = 1024;
+ char *buf = alloca (buflen);
+ size_t readlen = 0;
+ int fd = open ("/proc/self/cmdline", O_RDONLY);
+ if (fd == -1)
+ {
+ dbg_log (_("\
+cannot open /proc/self/cmdline: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ paranoia = 0;
+ return;
+ }
+
+ while (1)
+ {
+ ssize_t n = TEMP_FAILURE_RETRY (read (fd, buf + readlen,
+ buflen - readlen));
+ if (n == -1)
+ {
+ dbg_log (_("\
+cannot open /proc/self/cmdline: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ close (fd);
+ paranoia = 0;
+ return;
+ }
+
+ readlen += n;
+
+ if (readlen < buflen)
+ break;
+
+ /* We might have to extend the buffer. */
+ size_t old_buflen = buflen;
+ char *newp = extend_alloca (buf, buflen, 2 * buflen);
+ buf = memmove (newp, buf, old_buflen);
+ }
+
+ close (fd);
+
+ /* Parse the command line. Worst case scenario: every two
+ characters form one parameter (one character plus NUL). */
+ char **argv = alloca ((readlen / 2 + 1) * sizeof (argv[0]));
+ int argc = 0;
+
+ char *cp = buf;
+ while (cp < buf + readlen)
+ {
+ argv[argc++] = cp;
+ cp = (char *) rawmemchr (cp, '\0') + 1;
+ }
+ argv[argc] = NULL;
+
+ /* Second, change back to the old user if we changed it. */
+ if (server_user != NULL)
+ {
+ if (setuid (old_uid) != 0)
+ {
+ dbg_log (_("\
+cannot change to old UID: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ paranoia = 0;
+ return;
+ }
+
+ if (setgid (old_gid) != 0)
+ {
+ dbg_log (_("\
+cannot change to old GID: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ setuid (server_uid);
+ paranoia = 0;
+ return;
+ }
+ }
+
+ /* Next change back to the old working directory. */
+ if (chdir (oldcwd) == -1)
+ {
+ dbg_log (_("\
+cannot change to old working directory: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ if (server_user != NULL)
+ {
+ setuid (server_uid);
+ setgid (server_gid);
+ }
+ paranoia = 0;
+ return;
+ }
+
+ /* Synchronize memory. */
+ for (int cnt = 0; cnt < lastdb; ++cnt)
+ {
+ /* Make sure nobody keeps using the database. */
+ dbs[cnt].head->timestamp = 0;
+
+ if (dbs[cnt].persistent)
+ // XXX async OK?
+ msync (dbs[cnt].head, dbs[cnt].memsize, MS_ASYNC);
+ }
+
+ /* The preparations are done. */
+ execv ("/proc/self/exe", argv);
+
+ /* If we come here, we will never be able to re-exec. */
+ dbg_log (_("re-exec failed: %s; disabling paranoia mode"),
+ strerror (errno));
+
+ if (server_user != NULL)
+ {
+ setuid (server_uid);
+ setgid (server_gid);
+ }
+ chdir ("/");
+ paranoia = 0;
+}
+
+
+/* List of file descriptors. */
+struct fdlist
+{
+ int fd;
+ struct fdlist *next;
+};
+/* Memory allocated for the list. */
+static struct fdlist *fdlist;
+/* List of currently ready-to-read file descriptors. */
+static struct fdlist *readylist;
+
+/* Conditional variable and mutex to signal availability of entries in
+ READYLIST. The condvar is initialized dynamically since we might
+ use a different clock depending on availability. */
+static pthread_cond_t readylist_cond;
+static pthread_mutex_t readylist_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* The clock to use with the condvar. */
+static clockid_t timeout_clock = CLOCK_REALTIME;
+
+/* Number of threads ready to handle the READYLIST. */
+static unsigned long int nready;
+
+
/* This is the main loop. It is replicated in different threads but the
`poll' call makes sure only one thread handles an incoming connection. */
static void *
__attribute__ ((__noreturn__))
nscd_run (void *p)
{
- long int my_number = (long int) p;
- struct pollfd conn;
- int run_prune = my_number < lastdb && dbs[my_number].enabled;
- time_t next_prune = run_prune ? time (NULL) + CACHE_PRUNE_INTERVAL : 0;
- static unsigned long int nready;
+ const long int my_number = (long int) p;
+ const int run_prune = my_number < lastdb && dbs[my_number].enabled;
+ struct timespec prune_ts;
+ int to = 0;
+ char buf[256];
if (run_prune)
- setup_thread (&dbs[my_number]);
+ {
+ setup_thread (&dbs[my_number]);
- conn.fd = sock;
- conn.events = POLLRDNORM;
+ /* We are running. */
+ dbs[my_number].head->timestamp = time (NULL);
- while (1)
- {
- int nr;
- time_t now = 0;
+ if (clock_gettime (timeout_clock, &prune_ts) == -1)
+ /* Should never happen. */
+ abort ();
- /* One more thread available. */
- atomic_increment (&nready);
+ /* Compute timeout time. */
+ prune_ts.tv_sec += CACHE_PRUNE_INTERVAL;
+ }
+
+ /* Initial locking. */
+ pthread_mutex_lock (&readylist_lock);
- no_conn:
- do
+ /* One more thread available. */
+ ++nready;
+
+ while (1)
+ {
+ while (readylist == NULL)
{
- int timeout = -1;
if (run_prune)
{
- /* NB: we do not flush the timestamp update using msync since
- this value doesnot matter on disk. */
- dbs[my_number].head->timestamp = now = time (NULL);
- timeout = now < next_prune ? 1000 * (next_prune - now) : 0;
+ /* Wait, but not forever. */
+ to = pthread_cond_timedwait (&readylist_cond, &readylist_lock,
+ &prune_ts);
+
+ /* If we were woken and there is no work to be done,
+ just start pruning. */
+ if (readylist == NULL && to == ETIMEDOUT)
+ {
+ --nready;
+ pthread_mutex_unlock (&readylist_lock);
+ goto only_prune;
+ }
}
+ else
+ /* No need to timeout. */
+ pthread_cond_wait (&readylist_cond, &readylist_lock);
+ }
- nr = poll (&conn, 1, timeout);
+ struct fdlist *it = readylist->next;
+ if (readylist->next == readylist)
+ /* Just one entry on the list. */
+ readylist = NULL;
+ else
+ readylist->next = it->next;
- if (nr == 0)
- {
- /* The `poll' call timed out. It's time to clean up the
- cache. */
- atomic_decrement (&nready);
- assert (my_number < lastdb);
- prune_cache (&dbs[my_number], time(NULL));
- now = time (NULL);
- next_prune = now + CACHE_PRUNE_INTERVAL;
-
- goto try_get;
- }
- }
- while ((conn.revents & POLLRDNORM) == 0);
+ /* Extract the information and mark the record ready to be used
+ again. */
+ int fd = it->fd;
+ it->next = NULL;
- got_data:;
- /* We have a new incoming connection. Accept the connection. */
- int fd = TEMP_FAILURE_RETRY (accept (conn.fd, NULL, NULL));
- request_header req;
- char buf[256];
- uid_t uid = -1;
-#ifdef SO_PEERCRED
- pid_t pid = 0;
-#endif
+ /* One more thread available. */
+ --nready;
- if (__builtin_expect (fd, 0) < 0)
- {
- if (errno != EAGAIN && errno != EWOULDBLOCK)
- dbg_log (_("while accepting connection: %s"),
- strerror_r (errno, buf, sizeof (buf)));
- goto no_conn;
- }
+ /* We are done with the list. */
+ pthread_mutex_unlock (&readylist_lock);
- /* This thread is busy. */
- atomic_decrement (&nready);
+ /* We do not want to block on a short read or so. */
+ int fl = fcntl (fd, F_GETFL);
+ if (fl == -1 || fcntl (fd, F_SETFL, fl | O_NONBLOCK) == -1)
+ goto close_and_out;
/* Now read the request. */
+ request_header req;
if (__builtin_expect (TEMP_FAILURE_RETRY (read (fd, &req, sizeof (req)))
!= sizeof (req), 0))
{
+ /* We failed to read data. Note that this also might mean we
+ failed because we would have blocked. */
if (debug_level > 0)
dbg_log (_("short read while reading request: %s"),
strerror_r (errno, buf, sizeof (buf)));
- close (fd);
- continue;
+ goto close_and_out;
}
/* Check whether this is a valid request type. */
@@ -878,7 +1071,10 @@ nscd_run (void *p)
/* Some systems have no SO_PEERCRED implementation. They don't
care about security so we don't as well. */
+ uid_t uid = -1;
#ifdef SO_PEERCRED
+ pid_t pid = 0;
+
if (secure_in_use)
{
struct ucred caller;
@@ -909,8 +1105,9 @@ nscd_run (void *p)
/* It should not be possible to crash the nscd with a silly
request (i.e., a terribly large key). We limit the size to 1kb. */
+#define MAXKEYLEN 1024
if (__builtin_expect (req.key_len, 1) < 0
- || __builtin_expect (req.key_len, 1) > 1024)
+ || __builtin_expect (req.key_len, 1) > MAXKEYLEN)
{
if (debug_level > 0)
dbg_log (_("key length in request too long: %d"), req.key_len);
@@ -918,17 +1115,17 @@ nscd_run (void *p)
else
{
/* Get the key. */
- char keybuf[req.key_len];
+ char keybuf[MAXKEYLEN];
if (__builtin_expect (TEMP_FAILURE_RETRY (read (fd, keybuf,
req.key_len))
!= req.key_len, 0))
{
+ /* Again, this can also mean we would have blocked. */
if (debug_level > 0)
dbg_log (_("short read while reading request key: %s"),
strerror_r (errno, buf, sizeof (buf)));
- close (fd);
- continue;
+ goto close_and_out;
}
if (__builtin_expect (debug_level, 0) > 0)
@@ -952,44 +1149,380 @@ handle_request: request received (Version = %d)"), req.version);
/* We are done. */
close (fd);
- /* Just determine whether any data is present. We do this to
- measure whether clients are queued up. */
- try_get:
- nr = poll (&conn, 1, 0);
- if (nr != 0)
+ /* Check whether we should be pruning the cache. */
+ assert (run_prune || to == 0);
+ if (to == ETIMEDOUT)
{
- if (nready == 0)
- ++client_queued;
+ only_prune:
+ /* The pthread_cond_timedwait() call timed out. It is time
+ to clean up the cache. */
+ assert (my_number < lastdb);
+ prune_cache (&dbs[my_number],
+ prune_ts.tv_sec + (prune_ts.tv_nsec >= 500000000));
+
+ if (clock_gettime (timeout_clock, &prune_ts) == -1)
+ /* Should never happen. */
+ abort ();
+
+ /* Compute next timeout time. */
+ prune_ts.tv_sec += CACHE_PRUNE_INTERVAL;
+
+ /* In case the list is emtpy we do not want to run the prune
+ code right away again. */
+ to = 0;
+ }
+
+ /* Re-locking. */
+ pthread_mutex_lock (&readylist_lock);
+
+ /* One more thread available. */
+ ++nready;
+ }
+}
+
- atomic_increment (&nready);
+static unsigned int nconns;
+
+static void
+fd_ready (int fd)
+{
+ pthread_mutex_lock (&readylist_lock);
+
+ /* Find an empty entry in FDLIST. */
+ size_t inner;
+ for (inner = 0; inner < nconns; ++inner)
+ if (fdlist[inner].next == NULL)
+ break;
+ assert (inner < nconns);
- goto got_data;
+ fdlist[inner].fd = fd;
+
+ if (readylist == NULL)
+ readylist = fdlist[inner].next = &fdlist[inner];
+ else
+ {
+ fdlist[inner].next = readylist->next;
+ readylist = readylist->next = &fdlist[inner];
+ }
+
+ bool do_signal = true;
+ if (__builtin_expect (nready == 0, 0))
+ {
+ ++client_queued;
+ do_signal = false;
+
+ /* Try to start another thread to help out. */
+ pthread_t th;
+ if (nthreads < max_nthreads
+ && pthread_create (&th, &attr, nscd_run,
+ (void *) (long int) nthreads) == 0)
+ {
+ /* We got another thread. */
+ ++nthreads;
+ /* The new thread might new a kick. */
+ do_signal = true;
}
+
}
+
+ pthread_mutex_unlock (&readylist_lock);
+
+ /* Tell one of the worker threads there is work to do. */
+ if (do_signal)
+ pthread_cond_signal (&readylist_cond);
+}
+
+
+/* Check whether restarting should happen. */
+static inline int
+restart_p (time_t now)
+{
+ return (paranoia && readylist == NULL && nready == nthreads
+ && now >= restart_time);
}
+/* Array for times a connection was accepted. */
+static time_t *starttime;
+
+
+static void
+__attribute__ ((__noreturn__))
+main_loop_poll (void)
+{
+ struct pollfd *conns = (struct pollfd *) xmalloc (nconns
+ * sizeof (conns[0]));
+
+ conns[0].fd = sock;
+ conns[0].events = POLLRDNORM;
+ size_t nused = 1;
+ size_t firstfree = 1;
+
+ while (1)
+ {
+ /* Wait for any event. We wait at most a couple of seconds so
+ that we can check whether we should close any of the accepted
+ connections since we have not received a request. */
+#define MAX_ACCEPT_TIMEOUT 30
+#define MIN_ACCEPT_TIMEOUT 5
+#define MAIN_THREAD_TIMEOUT \
+ (MAX_ACCEPT_TIMEOUT * 1000 \
+ - ((MAX_ACCEPT_TIMEOUT - MIN_ACCEPT_TIMEOUT) * 1000 * nused) / (2 * nconns))
+
+ int n = poll (conns, nused, MAIN_THREAD_TIMEOUT);
+
+ time_t now = time (NULL);
+
+ /* If there is a descriptor ready for reading or there is a new
+ connection, process this now. */
+ if (n > 0)
+ {
+ if (conns[0].revents != 0)
+ {
+ /* We have a new incoming connection. Accept the connection. */
+ int fd = TEMP_FAILURE_RETRY (accept (sock, NULL, NULL));
+
+ /* use the descriptor if we have not reached the limit. */
+ if (fd >= 0 && firstfree < nconns)
+ {
+ conns[firstfree].fd = fd;
+ conns[firstfree].events = POLLRDNORM;
+ starttime[firstfree] = now;
+ if (firstfree >= nused)
+ nused = firstfree + 1;
+
+ do
+ ++firstfree;
+ while (firstfree < nused && conns[firstfree].fd != -1);
+ }
+
+ --n;
+ }
+
+ for (size_t cnt = 1; cnt < nused && n > 0; ++cnt)
+ if (conns[cnt].revents != 0)
+ {
+ fd_ready (conns[cnt].fd);
+
+ /* Clean up the CONNS array. */
+ conns[cnt].fd = -1;
+ if (cnt < firstfree)
+ firstfree = cnt;
+ if (cnt == nused - 1)
+ do
+ --nused;
+ while (conns[nused - 1].fd == -1);
+
+ --n;
+ }
+ }
+
+ /* Now find entries which have timed out. */
+ assert (nused > 0);
+
+ /* We make the timeout length depend on the number of file
+ descriptors currently used. */
+#define ACCEPT_TIMEOUT \
+ (MAX_ACCEPT_TIMEOUT \
+ - ((MAX_ACCEPT_TIMEOUT - MIN_ACCEPT_TIMEOUT) * nused) / nconns)
+ time_t laststart = now - ACCEPT_TIMEOUT;
+
+ for (size_t cnt = nused - 1; cnt > 0; --cnt)
+ {
+ if (conns[cnt].fd != -1 && starttime[cnt] < laststart)
+ {
+ /* Remove the entry, it timed out. */
+ (void) close (conns[cnt].fd);
+ conns[cnt].fd = -1;
+
+ if (cnt < firstfree)
+ firstfree = cnt;
+ if (cnt == nused - 1)
+ do
+ --nused;
+ while (conns[nused - 1].fd == -1);
+ }
+ }
+
+ if (restart_p (now))
+ restart ();
+ }
+}
+
+
+#ifdef HAVE_EPOLL
+static void
+main_loop_epoll (int efd)
+{
+ struct epoll_event ev = { 0, };
+ int nused = 1;
+ size_t highest = 0;
+
+ /* Add the socket. */
+ ev.events = EPOLLRDNORM;
+ ev.data.fd = sock;
+ if (epoll_ctl (efd, EPOLL_CTL_ADD, sock, &ev) == -1)
+ /* We cannot use epoll. */
+ return;
+
+ while (1)
+ {
+ struct epoll_event revs[100];
+# define nrevs (sizeof (revs) / sizeof (revs[0]))
+
+ int n = epoll_wait (efd, revs, nrevs, MAIN_THREAD_TIMEOUT);
+
+ time_t now = time (NULL);
+
+ for (int cnt = 0; cnt < n; ++cnt)
+ if (revs[cnt].data.fd == sock)
+ {
+ /* A new connection. */
+ int fd = TEMP_FAILURE_RETRY (accept (sock, NULL, NULL));
+
+ if (fd >= 0)
+ {
+ /* Try to add the new descriptor. */
+ ev.data.fd = fd;
+ if (fd >= nconns
+ || epoll_ctl (efd, EPOLL_CTL_ADD, fd, &ev) == -1)
+ /* The descriptor is too large or something went
+ wrong. Close the descriptor. */
+ close (fd);
+ else
+ {
+ /* Remember when we accepted the connection. */
+ starttime[fd] = now;
+
+ if (fd > highest)
+ highest = fd;
+
+ ++nused;
+ }
+ }
+ }
+ else
+ {
+ /* Remove the descriptor from the epoll descriptor. */
+ struct epoll_event ev = { 0, };
+ (void) epoll_ctl (efd, EPOLL_CTL_DEL, revs[cnt].data.fd, &ev);
+
+ /* Get a worked to handle the request. */
+ fd_ready (revs[cnt].data.fd);
+
+ /* Reset the time. */
+ starttime[revs[cnt].data.fd] = 0;
+ if (revs[cnt].data.fd == highest)
+ do
+ --highest;
+ while (highest > 0 && starttime[highest] == 0);
+
+ --nused;
+ }
+
+ /* Now look for descriptors for accepted connections which have
+ no reply in too long of a time. */
+ time_t laststart = now - ACCEPT_TIMEOUT;
+ for (int cnt = highest; cnt > STDERR_FILENO; --cnt)
+ if (cnt != sock && starttime[cnt] != 0 && starttime[cnt] < laststart)
+ {
+ /* We are waiting for this one for too long. Close it. */
+ struct epoll_event ev = {0, };
+ (void) epoll_ctl (efd, EPOLL_CTL_DEL, cnt, &ev);
+
+ (void) close (cnt);
+
+ starttime[cnt] = 0;
+ if (cnt == highest)
+ --highest;
+ }
+ else if (cnt != sock && starttime[cnt] == 0 && cnt == highest)
+ --highest;
+
+ if (restart_p (now))
+ restart ();
+ }
+}
+#endif
+
+
/* Start all the threads we want. The initial process is thread no. 1. */
void
start_threads (void)
{
- long int i;
- pthread_attr_t attr;
- pthread_t th;
+ /* Initialize the conditional variable we will use. The only
+ non-standard attribute we might use is the clock selection. */
+ pthread_condattr_t condattr;
+ pthread_condattr_init (&condattr);
+
+#if _POSIX_CLOCK_SELECTION >= 0 && _POSIX_MONOTONIC_CLOCK >= 0
+ /* Determine whether the monotonous clock is available. */
+ struct timespec dummy;
+ if (clock_getres (CLOCK_MONOTONIC, &dummy) == 0
+ && pthread_condattr_setclock (&condattr, CLOCK_MONOTONIC) == 0)
+ timeout_clock = CLOCK_MONOTONIC;
+#endif
+
+ pthread_cond_init (&readylist_cond, &condattr);
+ pthread_condattr_destroy (&condattr);
+
+ /* Create the attribute for the threads. They are all created
+ detached. */
pthread_attr_init (&attr);
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+ /* Use 1MB stacks, twice as much for 64-bit architectures. */
+ pthread_attr_setstacksize (&attr, 1024 * 1024 * (sizeof (void *) / 4));
/* We allow less than LASTDB threads only for debugging. */
if (debug_level == 0)
nthreads = MAX (nthreads, lastdb);
- for (i = 1; i < nthreads; ++i)
- pthread_create (&th, &attr, nscd_run, (void *) i);
+ int nfailed = 0;
+ for (long int i = 0; i < nthreads; ++i)
+ {
+ pthread_t th;
+ if (pthread_create (&th, &attr, nscd_run, (void *) (i - nfailed)) != 0)
+ ++nfailed;
+ }
+ if (nthreads - nfailed < lastdb)
+ {
+ /* We could not start enough threads. */
+ dbg_log (_("could only start %d threads; terminating"),
+ nthreads - nfailed);
+ exit (1);
+ }
- pthread_attr_destroy (&attr);
+ /* Determine how much room for descriptors we should initially
+ allocate. This might need to change later if we cap the number
+ with MAXCONN. */
+ const long int nfds = sysconf (_SC_OPEN_MAX);
+#define MINCONN 32
+#define MAXCONN 16384
+ if (nfds == -1 || nfds > MAXCONN)
+ nconns = MAXCONN;
+ else if (nfds < MINCONN)
+ nconns = MINCONN;
+ else
+ nconns = nfds;
+
+ /* We need memory to pass descriptors on to the worker threads. */
+ fdlist = (struct fdlist *) xcalloc (nconns, sizeof (fdlist[0]));
+ /* Array to keep track when connection was accepted. */
+ starttime = (time_t *) xcalloc (nconns, sizeof (starttime[0]));
+
+ /* In the main thread we execute the loop which handles incoming
+ connections. */
+#ifdef HAVE_EPOLL
+ int efd = epoll_create (100);
+ if (efd != -1)
+ {
+ main_loop_epoll (efd);
+ close (efd);
+ }
+#endif
- nscd_run ((void *) 0);
+ main_loop_poll ();
}
/* Look up the uid, gid, and supplementary groups to run nscd as. When
@@ -1010,6 +1543,13 @@ begin_drop_privileges (void)
server_uid = pwd->pw_uid;
server_gid = pwd->pw_gid;
+ /* Save the old UID/GID if we have to change back. */
+ if (paranoia)
+ {
+ old_uid = getuid ();
+ old_gid = getgid ();
+ }
+
if (getgrouplist (server_user, server_gid, NULL, &server_ngroups) == 0)
{
/* This really must never happen. */
diff --git a/nscd/dbg_log.c b/nscd/dbg_log.c
index bcd9426020..afa06dcbe9 100644
--- a/nscd/dbg_log.c
+++ b/nscd/dbg_log.c
@@ -61,7 +61,8 @@ dbg_log (const char *fmt,...)
if (debug_level > 0)
{
- snprintf (msg, sizeof (msg), "%d: %s\n", getpid (), msg2);
+ snprintf (msg, sizeof (msg), "%d: %s%s", getpid (), msg2,
+ msg2[strlen (msg2) - 1] == '\n' ? "" : "\n");
if (dbgout)
{
fputs (msg, dbgout);
@@ -71,9 +72,7 @@ dbg_log (const char *fmt,...)
fputs (msg, stderr);
}
else
- {
- snprintf (msg, sizeof (msg), "%d: %s", getpid (), msg2);
- syslog (LOG_NOTICE, "%s", msg);
- }
+ syslog (LOG_NOTICE, "%d %s", getpid (), msg2);
+
va_end (ap);
}
diff --git a/nscd/nscd.c b/nscd/nscd.c
index 2a4cb2291d..15a7ea2530 100644
--- a/nscd/nscd.c
+++ b/nscd/nscd.c
@@ -79,6 +79,13 @@ time_t start_time;
uintptr_t pagesize_m1;
+int paranoia;
+time_t restart_time;
+time_t restart_interval = RESTART_INTERVAL;
+const char *oldcwd;
+uid_t old_uid;
+gid_t old_gid;
+
static int check_pid (const char *file);
static int write_pid (const char *file);
@@ -255,6 +262,9 @@ main (int argc, char **argv)
signal (SIGTTIN, SIG_IGN);
signal (SIGTSTP, SIG_IGN);
}
+ else
+ /* In foreground mode we are not paranoid. */
+ paranoia = 0;
/* Start the SELinux AVC. */
if (selinux_enabled)
@@ -422,6 +432,7 @@ nscd_open_socket (void)
return sock;
}
+
/* Cleanup. */
void
termination_handler (int signum)
@@ -469,7 +480,11 @@ check_pid (const char *file)
n = fscanf (fp, "%d", &pid);
fclose (fp);
- if (n != 1 || kill (pid, 0) == 0)
+ /* If we cannot parse the file default to assuming nscd runs.
+ If the PID is alive, assume it is running. That all unless
+ the PID is the same as the current process' since tha latter
+ can mean we re-exec. */
+ if ((n != 1 || kill (pid, 0) == 0) && pid != getpid ())
return 1;
}
diff --git a/nscd/nscd.conf b/nscd/nscd.conf
index 0560beba0d..9cb9fa292d 100644
--- a/nscd/nscd.conf
+++ b/nscd/nscd.conf
@@ -7,11 +7,14 @@
#
# logfile <file>
# debug-level <level>
-# threads <#threads to use>
+# threads <initial #threads to use>
+# max-threads <maximum #threads to use>
# server-user <user to run server as instead of root>
# server-user is ignored if nscd is started with -S parameters
# stat-user <user who is allowed to request statistics>
# reload-count unlimited|<number>
+# paranoia <yes|no>
+# restart-interval <time in seconds>
#
# enable-cache <service> <yes|no>
# positive-time-to-live <service> <time in seconds>
@@ -27,10 +30,13 @@
# logfile /var/log/nscd.log
# threads 6
+# max-threads 128
server-user nscd
# stat-user nocpulse
debug-level 0
# reload-count 5
+ paranoia no
+# restart-interval 3600
enable-cache passwd yes
positive-time-to-live passwd 600
diff --git a/nscd/nscd.h b/nscd/nscd.h
index 3a9660d3ec..4e00f69fb9 100644
--- a/nscd/nscd.h
+++ b/nscd/nscd.h
@@ -50,6 +50,10 @@ typedef enum
#define DEFAULT_RELOAD_LIMIT 5
+/* Time before restarting the process in paranoia mode. */
+#define RESTART_INTERVAL (60 * 60)
+
+
/* Structure describing dynamic part of one database. */
struct database_dyn
{
@@ -98,8 +102,10 @@ extern const struct iovec grp_iov_disabled;
extern const struct iovec hst_iov_disabled;
-/* Number of threads to run. */
+/* Initial number of threads to run. */
extern int nthreads;
+/* Maximum number of threads to use. */
+extern int max_nthreads;
/* Tables for which we cache data with uid. */
extern int secure_in_use; /* Is one of the above 1? */
@@ -127,6 +133,19 @@ extern unsigned int reload_count;
/* Pagesize minus one. */
extern uintptr_t pagesize_m1;
+/* Nonzero if paranoia mode is enabled. */
+extern int paranoia;
+/* Time after which the process restarts. */
+extern time_t restart_time;
+/* How much time between restarts. */
+extern time_t restart_interval;
+/* Old current working directory. */
+extern const char *oldcwd;
+/* Old user and group ID. */
+extern uid_t old_uid;
+extern gid_t old_gid;
+
+
/* Prototypes for global functions. */
/* nscd.c */
diff --git a/nscd/nscd_conf.c b/nscd/nscd_conf.c
index 8a312ff459..2bca368de6 100644
--- a/nscd/nscd_conf.c
+++ b/nscd/nscd_conf.c
@@ -18,13 +18,15 @@
02111-1307 USA. */
#include <ctype.h>
+#include <errno.h>
+#include <libintl.h>
#include <malloc.h>
#include <pwd.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
-#include <libintl.h>
+#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
@@ -182,6 +184,10 @@ nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb])
if (nthreads == -1)
nthreads = MAX (atol (arg1), lastdb);
}
+ else if (strcmp (entry, "max-threads") == 0)
+ {
+ max_nthreads = MAX (atol (arg1), lastdb);
+ }
else if (strcmp (entry, "server-user") == 0)
{
if (!arg1)
@@ -191,7 +197,7 @@ nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb])
}
else if (strcmp (entry, "stat-user") == 0)
{
- if (!arg1)
+ if (arg1 == NULL)
dbg_log (_("Must specify user name for stat-user option"));
else
{
@@ -248,11 +254,45 @@ nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb])
dbg_log (_("invalid value for 'reload-count': %u"), count);
}
}
+ else if (strcmp (entry, "paranoia") == 0)
+ {
+ if (strcmp (arg1, "no") == 0)
+ paranoia = 0;
+ else if (strcmp (arg1, "yes") == 0)
+ paranoia = 1;
+ }
+ else if (strcmp (entry, "restart-interval") == 0)
+ {
+ if (arg1 != NULL)
+ restart_interval = atol (arg1);
+ else
+ dbg_log (_("Must specify value for restart-interval option"));
+ }
else
dbg_log (_("Unknown option: %s %s %s"), entry, arg1, arg2);
}
while (!feof_unlocked (fp));
+ if (paranoia)
+ {
+ restart_time = time (NULL) + restart_interval;
+
+ /* Save the old current workding directory if we are in paranoia
+ mode. We have to change back to it. */
+ oldcwd = get_current_dir_name ();
+ if (oldcwd == NULL)
+ {
+ dbg_log (_("\
+cannot get current working directory: %s; disabling paranoia mode"),
+ strerror (errno));
+ paranoia = 0;
+ }
+ }
+
+ /* Enforce sanity. */
+ if (max_nthreads < nthreads)
+ max_nthreads = nthreads;
+
/* Free the buffer. */
free (line);
/* Close configuration file. */
diff --git a/nscd/nscd_helper.c b/nscd/nscd_helper.c
index 3c8693a93d..0e16cb8aeb 100644
--- a/nscd/nscd_helper.c
+++ b/nscd/nscd_helper.c
@@ -160,7 +160,8 @@ get_mapping (request_type type, const char *key,
if (head.version != DB_VERSION || head.header_size != sizeof (head)
/* This really should not happen but who knows, maybe the update
thread got stuck. */
- || head.timestamp + MAPPING_TIMEOUT < time (NULL))
+ || (! head.nscd_certainly_running
+ && head.timestamp + MAPPING_TIMEOUT < time (NULL)))
goto out_close;
size_t size = (sizeof (head) + roundup (head.module * sizeof (ref_t), ALIGN)
diff --git a/nscd/nscd_initgroups.c b/nscd/nscd_initgroups.c
index d6cb00000e..ce44f654d7 100644
--- a/nscd/nscd_initgroups.c
+++ b/nscd/nscd_initgroups.c
@@ -81,8 +81,11 @@ __nscd_getgrouplist (const char *user, gid_t group, long int *size,
sock = __nscd_open_socket (user, userlen, INITGROUPS, &initgr_resp_mem,
sizeof (initgr_resp_mem));
if (sock == -1)
- /* nscd not running or wrong version or hosts caching disabled. */
- __nss_not_use_nscd_group = 1;
+ {
+ /* nscd not running or wrong version or hosts caching disabled. */
+ __nss_not_use_nscd_group = 1;
+ goto out;
+ }
initgr_resp = &initgr_resp_mem;
}
@@ -128,8 +131,12 @@ __nscd_getgrouplist (const char *user, gid_t group, long int *size,
}
}
else
- /* No group found yet. */
- retval = 0;
+ {
+ /* No group found yet. */
+ retval = 0;
+
+ assert (*size >= 1);
+ }
/* Check whether GROUP is part of the mix. If not, add it. */
if (retval >= 0)
diff --git a/nscd/nscd_stat.c b/nscd/nscd_stat.c
index 3e3be5bc8c..9231642278 100644
--- a/nscd/nscd_stat.c
+++ b/nscd/nscd_stat.c
@@ -143,6 +143,8 @@ receive_print_stats (void)
int fd;
int i;
uid_t uid = getuid ();
+ const char *yesstr = nl_langinfo (YESSTR);
+ const char *nostr = nl_langinfo (NOSTR);
/* Find out whether there is another user but root allowed to
request statistics. */
@@ -223,31 +225,34 @@ receive_print_stats (void)
else
printf (_(" %2lus server runtime\n"), diff);
- printf (_("%15lu number of times clients had to wait\n"),
- data.client_queued);
+ printf (_("%15d current number of threads\n"
+ "%15d maximum number of threads\n"
+ "%15lu number of times clients had to wait\n"
+ "%15s paranoia mode enabled\n"
+ "%15lu restart internal\n"),
+ nthreads, max_nthreads, data.client_queued,
+ paranoia ? yesstr : nostr, (unsigned long int) restart_interval);
for (i = 0; i < lastdb; ++i)
{
unsigned long int hit = data.dbs[i].poshit + data.dbs[i].neghit;
unsigned long int all = hit + data.dbs[i].posmiss + data.dbs[i].negmiss;
- const char *enabled = nl_langinfo (data.dbs[i].enabled ? YESSTR : NOSTR);
- const char *check_file = nl_langinfo (data.dbs[i].check_file
- ? YESSTR : NOSTR);
- const char *shared = nl_langinfo (data.dbs[i].shared ? YESSTR : NOSTR);
- const char *persistent = nl_langinfo (data.dbs[i].persistent
- ? YESSTR : NOSTR);
+ const char *enabled = data.dbs[i].enabled ? yesstr : nostr;
+ const char *check_file = data.dbs[i].check_file ? yesstr : nostr;
+ const char *shared = data.dbs[i].shared ? yesstr : nostr;
+ const char *persistent = data.dbs[i].persistent ? yesstr : nostr;
if (enabled[0] == '\0')
/* The locale does not provide this information so we have to
translate it ourself. Since we should avoid short translation
terms we artifically increase the length. */
- enabled = data.dbs[i].enabled ? _(" yes") : _(" no");
+ enabled = data.dbs[i].enabled ? yesstr : nostr;
if (check_file[0] == '\0')
- check_file = data.dbs[i].check_file ? _(" yes") : _(" no");
+ check_file = data.dbs[i].check_file ? yesstr : nostr;
if (shared[0] == '\0')
- shared = data.dbs[i].shared ? _(" yes") : _(" no");
+ shared = data.dbs[i].shared ? yesstr : nostr;
if (persistent[0] == '\0')
- persistent = data.dbs[i].persistent ? _(" yes") : _(" no");
+ persistent = data.dbs[i].persistent ? yesstr : nostr;
if (all == 0)
/* If nothing happened so far report a 0% hit rate. */
diff --git a/nscd/selinux.c b/nscd/selinux.c
index 77651e05c3..f57f0920ae 100644
--- a/nscd/selinux.c
+++ b/nscd/selinux.c
@@ -207,8 +207,8 @@ nscd_request_avc_has_perm (int fd, request_type req)
dbg_log (_("Error getting context of nscd"));
goto out;
}
- if (avc_context_to_sid (scon, &ssid) < 0 ||
- avc_context_to_sid (tcon, &tsid) < 0)
+ if (avc_context_to_sid (scon, &ssid) < 0
+ || avc_context_to_sid (tcon, &tsid) < 0)
{
dbg_log (_("Error getting sid from context"));
goto out;