diff options
Diffstat (limited to 'REORG.TODO/nscd')
45 files changed, 13874 insertions, 0 deletions
diff --git a/REORG.TODO/nscd/Depend b/REORG.TODO/nscd/Depend new file mode 100644 index 0000000000..6c1aa44e6e --- /dev/null +++ b/REORG.TODO/nscd/Depend @@ -0,0 +1 @@ +nptl diff --git a/REORG.TODO/nscd/Makefile b/REORG.TODO/nscd/Makefile new file mode 100644 index 0000000000..4126996887 --- /dev/null +++ b/REORG.TODO/nscd/Makefile @@ -0,0 +1,103 @@ +# Copyright (C) 1998-2017 Free Software Foundation, Inc. +# This file is part of the GNU C Library. + +# The GNU C 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. + +# The GNU C 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 the GNU C Library; if not, see +# <http://www.gnu.org/licenses/>. + +# +# Sub-makefile for nscd portion of the library. +# +subdir := nscd + +include ../Makeconfig + +ifneq ($(use-nscd),no) +routines := nscd_getpw_r nscd_getgr_r nscd_gethst_r nscd_getai \ + nscd_initgroups nscd_getserv_r nscd_netgroup +aux := nscd_helper +endif + +# To find xmalloc.c +vpath %.c ../locale/programs + +nscd-modules := nscd connections pwdcache getpwnam_r getpwuid_r grpcache \ + getgrnam_r getgrgid_r hstcache gethstbyad_r gethstbynm3_r \ + getsrvbynm_r getsrvbypt_r servicescache \ + dbg_log nscd_conf nscd_stat cache mem nscd_setup_thread \ + xmalloc xstrdup aicache initgrcache gai res_hconf \ + netgroupcache + +ifeq ($(build-nscd)$(have-thread-library),yesyes) + +others += nscd +others-pie += nscd +install-sbin := nscd + +extra-objs = $(nscd-modules:=.o) + +endif + +all-nscd-modules := $(nscd-modules) selinux +ifeq (yes,$(have-selinux)) +ifeq (yes,$(have-libaudit)) +libaudit = -laudit +ifeq (yes,$(have-libcap)) +libcap = -lcap +endif +endif + +nscd-modules += selinux +selinux-LIBS := -lselinux $(libaudit) $(libcap) + +# The configure.ac check for libselinux and its headers did not use +# $SYSINCLUDES. The directory specified by --with-headers usually +# contains only the basic kernel interface headers, not something like +# libselinux. So the simplest thing is to presume that the standard +# system headers will be ok for this file. +$(objpfx)nscd_stat.o: sysincludes = # nothing +$(objpfx)selinux.o: sysincludes = # nothing +endif + +LDLIBS-nscd = $(selinux-LIBS) + +include ../Rules + +CFLAGS-nscd_getpw_r.c = -fexceptions +CFLAGS-nscd_getgr_r.c = -fexceptions +CFLAGS-nscd_gethst_r.c = -fexceptions +CFLAGS-nscd_getai.c = -fexceptions +CFLAGS-nscd_initgroups.c = -fexceptions + +CPPFLAGS-nscd += -D_FORTIFY_SOURCE=2 + +ifeq (yesyes,$(have-fpie)$(build-shared)) +CFLAGS-nscd += $(pie-ccflag) +endif + +ifeq (yesyes,$(have-fpie)$(build-shared)) +LDFLAGS-nscd = -Wl,-z,now +endif + +# Set libof-nscd. +cpp-srcs-left := $(nscd-modules) +lib := nscd +include $(patsubst %,$(..)libof-iterator.mk,$(cpp-srcs-left)) + +$(objpfx)nscd: $(nscd-modules:%=$(objpfx)%.o) + +ifeq ($(build-shared),yes) +$(objpfx)nscd: $(shared-thread-library) $(common-objpfx)nis/libnsl.so +else +$(objpfx)nscd: $(static-thread-library) $(common-objpfx)nis/libnsl.a +endif diff --git a/REORG.TODO/nscd/aicache.c b/REORG.TODO/nscd/aicache.c new file mode 100644 index 0000000000..7bf4131979 --- /dev/null +++ b/REORG.TODO/nscd/aicache.c @@ -0,0 +1,585 @@ +/* Cache handling for host lookup. + Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2004. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <netdb.h> +#include <nss.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/mman.h> +#include <resolv/resolv-internal.h> +#include <resolv/res_hconf.h> + +#include "dbg_log.h" +#include "nscd.h" +#ifdef HAVE_SENDFILE +# include <kernel-features.h> +#endif + + +typedef enum nss_status (*nss_gethostbyname4_r) + (const char *name, struct gaih_addrtuple **pat, + char *buffer, size_t buflen, int *errnop, + int *h_errnop, int32_t *ttlp); +typedef enum nss_status (*nss_gethostbyname3_r) + (const char *name, int af, struct hostent *host, + char *buffer, size_t buflen, int *errnop, + int *h_errnop, int32_t *, char **); +typedef enum nss_status (*nss_getcanonname_r) + (const char *name, char *buffer, size_t buflen, char **result, + int *errnop, int *h_errnop); + + +static const ai_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .naddrs = 0, + .addrslen = 0, + .canonlen = 0, + .error = 0 +}; + + +static time_t +addhstaiX (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid, struct hashentry *const he, + struct datahead *dh) +{ + /* Search for the entry matching the key. Please note that we don't + look again in the table whether the dataset is now available. We + simply insert it. It does not matter if it is in there twice. The + pruning function only will look at the timestamp. */ + + /* We allocate all data in one memory block: the iov vector, + the response header and the dataset itself. */ + struct dataset + { + struct datahead head; + ai_response_header resp; + char strdata[0]; + } *dataset = NULL; + + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in hosts cache!"), (char *) key); + else + dbg_log (_("Reloading \"%s\" in hosts cache!"), (char *) key); + } + + static service_user *hosts_database; + service_user *nip; + int no_more; + int rc6 = 0; + int rc4 = 0; + int herrno = 0; + + if (hosts_database == NULL) + no_more = __nss_database_lookup ("hosts", NULL, + "dns [!UNAVAIL=return] files", + &hosts_database); + else + no_more = 0; + nip = hosts_database; + + /* Initialize configurations. */ + _res_hconf_init (); + if (__res_maybe_init (&_res, 0) == -1) + no_more = 1; + + /* If we are looking for both IPv4 and IPv6 address we don't want + the lookup functions to automatically promote IPv4 addresses to + IPv6 addresses. Currently this is decided by setting the + RES_USE_INET6 bit in _res.options. */ + int old_res_options = _res.options; + _res.options &= ~DEPRECATED_RES_USE_INET6; + + size_t tmpbuf6len = 1024; + char *tmpbuf6 = alloca (tmpbuf6len); + size_t tmpbuf4len = 0; + char *tmpbuf4 = NULL; + int32_t ttl = INT32_MAX; + ssize_t total = 0; + char *key_copy = NULL; + bool alloca_used = false; + time_t timeout = MAX_TIMEOUT_VALUE; + + while (!no_more) + { + void *cp; + int status[2] = { NSS_STATUS_UNAVAIL, NSS_STATUS_UNAVAIL }; + int naddrs = 0; + size_t addrslen = 0; + char *canon = NULL; + size_t canonlen; + + nss_gethostbyname4_r fct4 = __nss_lookup_function (nip, + "gethostbyname4_r"); + if (fct4 != NULL) + { + struct gaih_addrtuple atmem; + struct gaih_addrtuple *at; + while (1) + { + at = &atmem; + rc6 = 0; + herrno = 0; + status[1] = DL_CALL_FCT (fct4, (key, &at, tmpbuf6, tmpbuf6len, + &rc6, &herrno, &ttl)); + if (rc6 != ERANGE || (herrno != NETDB_INTERNAL + && herrno != TRY_AGAIN)) + break; + tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len); + } + + if (rc6 != 0 && herrno == NETDB_INTERNAL) + goto out; + + if (status[1] != NSS_STATUS_SUCCESS) + goto next_nip; + + /* We found the data. Count the addresses and the size. */ + for (const struct gaih_addrtuple *at2 = at = &atmem; at2 != NULL; + at2 = at2->next) + { + ++naddrs; + /* We do not handle anything other than IPv4 and IPv6 + addresses. The getaddrinfo implementation does not + either so it is not worth trying to do more. */ + if (at2->family == AF_INET) + addrslen += INADDRSZ; + else if (at2->family == AF_INET6) + addrslen += IN6ADDRSZ; + } + canon = at->name; + canonlen = strlen (canon) + 1; + + total = sizeof (*dataset) + naddrs + addrslen + canonlen; + + /* Now we can allocate the data structure. If the TTL of the + entry is reported as zero do not cache the entry at all. */ + if (ttl != 0 && he == NULL) + dataset = (struct dataset *) mempool_alloc (db, total + + req->key_len, 1); + + if (dataset == NULL) + { + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + dataset = (struct dataset *) alloca (total + req->key_len); + + /* We cannot add this record to the permanent database. */ + alloca_used = true; + } + + /* Fill in the address and address families. */ + char *addrs = dataset->strdata; + uint8_t *family = (uint8_t *) (addrs + addrslen); + + for (const struct gaih_addrtuple *at2 = at; at2 != NULL; + at2 = at2->next) + { + *family++ = at2->family; + if (at2->family == AF_INET) + addrs = mempcpy (addrs, at2->addr, INADDRSZ); + else if (at2->family == AF_INET6) + addrs = mempcpy (addrs, at2->addr, IN6ADDRSZ); + } + + cp = family; + } + else + { + /* Prefer the function which also returns the TTL and + canonical name. */ + nss_gethostbyname3_r fct = __nss_lookup_function (nip, + "gethostbyname3_r"); + if (fct == NULL) + fct = __nss_lookup_function (nip, "gethostbyname2_r"); + + if (fct == NULL) + goto next_nip; + + struct hostent th[2]; + + /* Collect IPv6 information first. */ + while (1) + { + rc6 = 0; + status[0] = DL_CALL_FCT (fct, (key, AF_INET6, &th[0], tmpbuf6, + tmpbuf6len, &rc6, &herrno, &ttl, + &canon)); + if (rc6 != ERANGE || herrno != NETDB_INTERNAL) + break; + tmpbuf6 = extend_alloca (tmpbuf6, tmpbuf6len, 2 * tmpbuf6len); + } + + if (rc6 != 0 && herrno == NETDB_INTERNAL) + goto out; + + /* If the IPv6 lookup has been successful do not use the + buffer used in that lookup, use a new one. */ + if (status[0] == NSS_STATUS_SUCCESS && rc6 == 0) + { + tmpbuf4len = 512; + tmpbuf4 = alloca (tmpbuf4len); + } + else + { + tmpbuf4len = tmpbuf6len; + tmpbuf4 = tmpbuf6; + } + + /* Next collect IPv4 information. */ + while (1) + { + rc4 = 0; + status[1] = DL_CALL_FCT (fct, (key, AF_INET, &th[1], tmpbuf4, + tmpbuf4len, &rc4, &herrno, + ttl == INT32_MAX ? &ttl : NULL, + canon == NULL ? &canon : NULL)); + if (rc4 != ERANGE || herrno != NETDB_INTERNAL) + break; + tmpbuf4 = extend_alloca (tmpbuf4, tmpbuf4len, 2 * tmpbuf4len); + } + + if (rc4 != 0 && herrno == NETDB_INTERNAL) + goto out; + + if (status[0] != NSS_STATUS_SUCCESS + && status[1] != NSS_STATUS_SUCCESS) + goto next_nip; + + /* We found the data. Count the addresses and the size. */ + for (int j = 0; j < 2; ++j) + if (status[j] == NSS_STATUS_SUCCESS) + for (int i = 0; th[j].h_addr_list[i] != NULL; ++i) + { + ++naddrs; + addrslen += th[j].h_length; + } + + if (canon == NULL) + { + /* Determine the canonical name. */ + nss_getcanonname_r cfct; + cfct = __nss_lookup_function (nip, "getcanonname_r"); + if (cfct != NULL) + { + const size_t max_fqdn_len = 256; + char *buf = alloca (max_fqdn_len); + char *s; + int rc; + + if (DL_CALL_FCT (cfct, (key, buf, max_fqdn_len, &s, + &rc, &herrno)) + == NSS_STATUS_SUCCESS) + canon = s; + else + /* Set to name now to avoid using gethostbyaddr. */ + canon = key; + } + else + { + struct hostent *hstent = NULL; + int herrno; + struct hostent hstent_mem; + void *addr; + size_t addrlen; + int addrfamily; + + if (status[1] == NSS_STATUS_SUCCESS) + { + addr = th[1].h_addr_list[0]; + addrlen = sizeof (struct in_addr); + addrfamily = AF_INET; + } + else + { + addr = th[0].h_addr_list[0]; + addrlen = sizeof (struct in6_addr); + addrfamily = AF_INET6; + } + + size_t tmpbuflen = 512; + char *tmpbuf = alloca (tmpbuflen); + int rc; + while (1) + { + rc = __gethostbyaddr2_r (addr, addrlen, addrfamily, + &hstent_mem, tmpbuf, tmpbuflen, + &hstent, &herrno, NULL); + if (rc != ERANGE || herrno != NETDB_INTERNAL) + break; + tmpbuf = extend_alloca (tmpbuf, tmpbuflen, + tmpbuflen * 2); + } + + if (rc == 0) + { + if (hstent != NULL) + canon = hstent->h_name; + else + canon = key; + } + } + } + + canonlen = canon == NULL ? 0 : (strlen (canon) + 1); + + total = sizeof (*dataset) + naddrs + addrslen + canonlen; + + + /* Now we can allocate the data structure. If the TTL of the + entry is reported as zero do not cache the entry at all. */ + if (ttl != 0 && he == NULL) + dataset = (struct dataset *) mempool_alloc (db, total + + req->key_len, 1); + + if (dataset == NULL) + { + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + dataset = (struct dataset *) alloca (total + req->key_len); + + /* We cannot add this record to the permanent database. */ + alloca_used = true; + } + + /* Fill in the address and address families. */ + char *addrs = dataset->strdata; + uint8_t *family = (uint8_t *) (addrs + addrslen); + + for (int j = 0; j < 2; ++j) + if (status[j] == NSS_STATUS_SUCCESS) + for (int i = 0; th[j].h_addr_list[i] != NULL; ++i) + { + addrs = mempcpy (addrs, th[j].h_addr_list[i], + th[j].h_length); + *family++ = th[j].h_addrtype; + } + + cp = family; + } + + timeout = datahead_init_pos (&dataset->head, total + req->key_len, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + ttl == INT32_MAX ? db->postimeout : ttl); + + /* Fill in the rest of the dataset. */ + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.naddrs = naddrs; + dataset->resp.addrslen = addrslen; + dataset->resp.canonlen = canonlen; + dataset->resp.error = NETDB_SUCCESS; + + if (canon != NULL) + cp = mempcpy (cp, canon, canonlen); + + key_copy = memcpy (cp, key, req->key_len); + + assert (cp == (char *) dataset + total); + + /* Now we can determine whether on refill we have to create a + new record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (total + req->key_len == dh->allocsize + && total - offsetof (struct dataset, resp) == dh->recsize + && memcmp (&dataset->resp, dh->data, + dh->allocsize - offsetof (struct dataset, + resp)) == 0) + { + /* The data has not changed. We will just bump the + timeout value. Note that the new record has been + allocated on the stack and need not be freed. */ + dh->timeout = dataset->head.timeout; + dh->ttl = dataset->head.ttl; + ++dh->nreloads; + } + else + { + /* We have to create a new record. Just allocate + appropriate memory and copy it. */ + struct dataset *newp + = (struct dataset *) mempool_alloc (db, total + req->key_len, + 1); + if (__glibc_likely (newp != NULL)) + { + /* Adjust pointer into the memory block. */ + key_copy = (char *) newp + (key_copy - (char *) dataset); + + dataset = memcpy (newp, dataset, total + req->key_len); + alloca_used = false; + } + + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + else + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so + would unnecessarily let the receiver wait. */ + assert (fd != -1); + +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && !alloca_used) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + total + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); +# ifndef __ASSUME_SENDFILE + ssize_t written; + written = +# endif + sendfileall (fd, db->wr_fd, (char *) &dataset->resp + - (char *) db->head, dataset->head.recsize); +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + } + else +# ifndef __ASSUME_SENDFILE + use_write: +# endif +#endif + writeall (fd, &dataset->resp, dataset->head.recsize); + } + + goto out; + +next_nip: + if (nss_next_action (nip, status[1]) == NSS_ACTION_RETURN) + break; + + if (nip->next == NULL) + no_more = -1; + else + nip = nip->next; + } + + /* No result found. Create a negative result record. */ + if (he != NULL && rc4 == EAGAIN) + { + /* If we have an old record available but cannot find one now + because the service is not available we keep the old record + and make sure it does not get removed. */ + if (reload_count != UINT_MAX && dh->nreloads == reload_count) + /* Do not reset the value if we never not reload the record. */ + dh->nreloads = reload_count - 1; + + /* Reload with the same time-to-live value. */ + timeout = dh->timeout = time (NULL) + dh->ttl; + } + else + { + /* We have no data. This means we send the standard reply for + this case. */ + total = sizeof (notfound); + + if (fd != -1) + TEMP_FAILURE_RETRY (send (fd, ¬found, total, MSG_NOSIGNAL)); + + /* If we have a transient error or cannot permanently store the + result, so be it. */ + if (rc4 == EAGAIN || __builtin_expect (db->negtimeout == 0, 0)) + { + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + dataset = NULL; + } + else if ((dataset = mempool_alloc (db, (sizeof (struct dataset) + + req->key_len), 1)) != NULL) + { + timeout = datahead_init_neg (&dataset->head, + sizeof (struct dataset) + req->key_len, + total, db->negtimeout); + + /* This is the reply. */ + memcpy (&dataset->resp, ¬found, total); + + /* Copy the key data. */ + key_copy = memcpy (dataset->strdata, key, req->key_len); + } + } + + out: + _res.options |= old_res_options & DEPRECATED_RES_USE_INET6; + + if (dataset != NULL && !alloca_used) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + total + req->key_len, + MS_ASYNC); + } + + (void) cache_add (req->type, key_copy, req->key_len, &dataset->head, + true, db, uid, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + + return timeout; +} + + +void +addhstai (struct database_dyn *db, int fd, request_header *req, void *key, + uid_t uid) +{ + addhstaiX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdhstai (struct database_dyn *db, struct hashentry *he, struct datahead *dh) +{ + request_header req = + { + .type = GETAI, + .key_len = he->len + }; + + return addhstaiX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} diff --git a/REORG.TODO/nscd/cache.c b/REORG.TODO/nscd/cache.c new file mode 100644 index 0000000000..b9dbc7a0bd --- /dev/null +++ b/REORG.TODO/nscd/cache.c @@ -0,0 +1,540 @@ +/* Copyright (c) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <atomic.h> +#include <errno.h> +#include <error.h> +#include <inttypes.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <libintl.h> +#include <arpa/inet.h> +#include <rpcsvc/nis.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/uio.h> + +#include "nscd.h" +#include "dbg_log.h" + + +/* Wrapper functions with error checking for standard functions. */ +extern void *xcalloc (size_t n, size_t s); + + +/* Number of times a value is reloaded without being used. UINT_MAX + means unlimited. */ +unsigned int reload_count = DEFAULT_RELOAD_LIMIT; + + +static time_t (*const readdfcts[LASTREQ]) (struct database_dyn *, + struct hashentry *, + struct datahead *) = +{ + [GETPWBYNAME] = readdpwbyname, + [GETPWBYUID] = readdpwbyuid, + [GETGRBYNAME] = readdgrbyname, + [GETGRBYGID] = readdgrbygid, + [GETHOSTBYNAME] = readdhstbyname, + [GETHOSTBYNAMEv6] = readdhstbynamev6, + [GETHOSTBYADDR] = readdhstbyaddr, + [GETHOSTBYADDRv6] = readdhstbyaddrv6, + [GETAI] = readdhstai, + [INITGROUPS] = readdinitgroups, + [GETSERVBYNAME] = readdservbyname, + [GETSERVBYPORT] = readdservbyport, + [GETNETGRENT] = readdgetnetgrent, + [INNETGR] = readdinnetgr +}; + + +/* Search the cache for a matching entry and return it when found. If + this fails search the negative cache and return (void *) -1 if this + search was successful. Otherwise return NULL. + + This function must be called with the read-lock held. */ +struct datahead * +cache_search (request_type type, const void *key, size_t len, + struct database_dyn *table, uid_t owner) +{ + unsigned long int hash = __nis_hash (key, len) % table->head->module; + + unsigned long int nsearched = 0; + struct datahead *result = NULL; + + ref_t work = table->head->array[hash]; + while (work != ENDREF) + { + ++nsearched; + + struct hashentry *here = (struct hashentry *) (table->data + work); + + if (type == here->type && len == here->len + && memcmp (key, table->data + here->key, len) == 0 + && here->owner == owner) + { + /* We found the entry. Increment the appropriate counter. */ + struct datahead *dh + = (struct datahead *) (table->data + here->packet); + + /* See whether we must ignore the entry. */ + if (dh->usable) + { + /* We do not synchronize the memory here. The statistics + data is not crucial, we synchronize only once in a while + in the cleanup threads. */ + if (dh->notfound) + ++table->head->neghit; + else + { + ++table->head->poshit; + + if (dh->nreloads != 0) + dh->nreloads = 0; + } + + result = dh; + break; + } + } + + work = here->next; + } + + if (nsearched > table->head->maxnsearched) + table->head->maxnsearched = nsearched; + + return result; +} + +/* Add a new entry to the cache. The return value is zero if the function + call was successful. + + This function must be called with the read-lock held. + + We modify the table but we nevertheless only acquire a read-lock. + This is ok since we use operations which would be safe even without + locking, given that the `prune_cache' function never runs. Using + the readlock reduces the chance of conflicts. */ +int +cache_add (int type, const void *key, size_t len, struct datahead *packet, + bool first, struct database_dyn *table, + uid_t owner, bool prune_wakeup) +{ + if (__glibc_unlikely (debug_level >= 2)) + { + const char *str; + char buf[INET6_ADDRSTRLEN + 1]; + if (type == GETHOSTBYADDR || type == GETHOSTBYADDRv6) + str = inet_ntop (type == GETHOSTBYADDR ? AF_INET : AF_INET6, + key, buf, sizeof (buf)); + else + str = key; + + dbg_log (_("add new entry \"%s\" of type %s for %s to cache%s"), + str, serv2str[type], dbnames[table - dbs], + first ? _(" (first)") : ""); + } + + unsigned long int hash = __nis_hash (key, len) % table->head->module; + struct hashentry *newp; + + newp = mempool_alloc (table, sizeof (struct hashentry), 0); + /* If we cannot allocate memory, just do not do anything. */ + if (newp == NULL) + { + /* If necessary mark the entry as unusable so that lookups will + not use it. */ + if (first) + packet->usable = false; + + return -1; + } + + newp->type = type; + newp->first = first; + newp->len = len; + newp->key = (char *) key - table->data; + assert (newp->key + newp->len <= table->head->first_free); + newp->owner = owner; + newp->packet = (char *) packet - table->data; + assert ((newp->packet & BLOCK_ALIGN_M1) == 0); + + /* Put the new entry in the first position. */ + /* TODO Review concurrency. Use atomic_exchange_release. */ + newp->next = atomic_load_relaxed (&table->head->array[hash]); + while (!atomic_compare_exchange_weak_release (&table->head->array[hash], + (ref_t *) &newp->next, + (ref_t) ((char *) newp + - table->data))); + + /* Update the statistics. */ + if (packet->notfound) + ++table->head->negmiss; + else if (first) + ++table->head->posmiss; + + /* We depend on this value being correct and at least as high as the + real number of entries. */ + atomic_increment (&table->head->nentries); + + /* It does not matter that we are not loading the just increment + value, this is just for statistics. */ + unsigned long int nentries = table->head->nentries; + if (nentries > table->head->maxnentries) + table->head->maxnentries = nentries; + + if (table->persistent) + // XXX async OK? + msync ((void *) table->head, + (char *) &table->head->array[hash] - (char *) table->head + + sizeof (ref_t), MS_ASYNC); + + /* We do not have to worry about the pruning thread if we are + re-adding the data since this is done by the pruning thread. We + also do not have to do anything in case this is not the first + time the data is entered since different data heads all have the + same timeout. */ + if (first && prune_wakeup) + { + /* Perhaps the prune thread for the table is not running in a long + time. Wake it if necessary. */ + pthread_mutex_lock (&table->prune_lock); + time_t next_wakeup = table->wakeup_time; + bool do_wakeup = false; + if (next_wakeup > packet->timeout + CACHE_PRUNE_INTERVAL) + { + table->wakeup_time = packet->timeout; + do_wakeup = true; + } + pthread_mutex_unlock (&table->prune_lock); + if (do_wakeup) + pthread_cond_signal (&table->prune_cond); + } + + return 0; +} + +/* Walk through the table and remove all entries which lifetime ended. + + We have a problem here. To actually remove the entries we must get + the write-lock. But since we want to keep the time we have the + lock as short as possible we cannot simply acquire the lock when we + start looking for timedout entries. + + Therefore we do it in two stages: first we look for entries which + must be invalidated and remember them. Then we get the lock and + actually remove them. This is complicated by the way we have to + free the data structures since some hash table entries share the same + data. */ +time_t +prune_cache (struct database_dyn *table, time_t now, int fd) +{ + size_t cnt = table->head->module; + + /* If this table is not actually used don't do anything. */ + if (cnt == 0) + { + if (fd != -1) + { + /* Reply to the INVALIDATE initiator. */ + int32_t resp = 0; + writeall (fd, &resp, sizeof (resp)); + } + + /* No need to do this again anytime soon. */ + return 24 * 60 * 60; + } + + /* If we check for the modification of the underlying file we invalidate + the entries also in this case. */ + if (table->check_file && now != LONG_MAX) + { + struct traced_file *runp = table->traced_files; + + while (runp != NULL) + { +#ifdef HAVE_INOTIFY + if (runp->inotify_descr[TRACED_FILE] == -1) +#endif + { + struct stat64 st; + + if (stat64 (runp->fname, &st) < 0) + { + /* Print a diagnostic that the traced file was missing. + We must not disable tracing since the file might return + shortly and we want to reload it at the next pruning. + Disabling tracing here would go against the configuration + as specified by the user via check-files. */ + char buf[128]; + dbg_log (_("checking for monitored file `%s': %s"), + runp->fname, strerror_r (errno, buf, sizeof (buf))); + } + else + { + /* This must be `!=` to catch cases where users turn the + clocks back and we still want to detect any time difference + in mtime. */ + if (st.st_mtime != runp->mtime) + { + dbg_log (_("monitored file `%s` changed (mtime)"), + runp->fname); + /* The file changed. Invalidate all entries. */ + now = LONG_MAX; + runp->mtime = st.st_mtime; +#ifdef HAVE_INOTIFY + /* Attempt to install a watch on the file. */ + install_watches (runp); +#endif + } + } + } + + runp = runp->next; + } + } + + /* We run through the table and find values which are not valid anymore. + + Note that for the initial step, finding the entries to be removed, + we don't need to get any lock. It is at all timed assured that the + linked lists are set up correctly and that no second thread prunes + the cache. */ + bool *mark; + size_t memory_needed = cnt * sizeof (bool); + bool mark_use_alloca; + if (__glibc_likely (memory_needed <= MAX_STACK_USE)) + { + mark = alloca (cnt * sizeof (bool)); + memset (mark, '\0', memory_needed); + mark_use_alloca = true; + } + else + { + mark = xcalloc (1, memory_needed); + mark_use_alloca = false; + } + size_t first = cnt + 1; + size_t last = 0; + char *const data = table->data; + bool any = false; + + if (__glibc_unlikely (debug_level > 2)) + dbg_log (_("pruning %s cache; time %ld"), + dbnames[table - dbs], (long int) now); + +#define NO_TIMEOUT LONG_MAX + time_t next_timeout = NO_TIMEOUT; + do + { + ref_t run = table->head->array[--cnt]; + + while (run != ENDREF) + { + struct hashentry *runp = (struct hashentry *) (data + run); + struct datahead *dh = (struct datahead *) (data + runp->packet); + + /* Some debug support. */ + if (__glibc_unlikely (debug_level > 2)) + { + char buf[INET6_ADDRSTRLEN]; + const char *str; + + if (runp->type == GETHOSTBYADDR || runp->type == GETHOSTBYADDRv6) + { + inet_ntop (runp->type == GETHOSTBYADDR ? AF_INET : AF_INET6, + data + runp->key, buf, sizeof (buf)); + str = buf; + } + else + str = data + runp->key; + + dbg_log (_("considering %s entry \"%s\", timeout %" PRIu64), + serv2str[runp->type], str, dh->timeout); + } + + /* Check whether the entry timed out. */ + if (dh->timeout < now) + { + /* This hash bucket could contain entries which need to + be looked at. */ + mark[cnt] = true; + + first = MIN (first, cnt); + last = MAX (last, cnt); + + /* We only have to look at the data of the first entries + since the count information is kept in the data part + which is shared. */ + if (runp->first) + { + + /* At this point there are two choices: we reload the + value or we discard it. Do not change NRELOADS if + we never not reload the record. */ + if ((reload_count != UINT_MAX + && __builtin_expect (dh->nreloads >= reload_count, 0)) + /* We always remove negative entries. */ + || dh->notfound + /* Discard everything if the user explicitly + requests it. */ + || now == LONG_MAX) + { + /* Remove the value. */ + dh->usable = false; + + /* We definitely have some garbage entries now. */ + any = true; + } + else + { + /* Reload the value. We do this only for the + initially used key, not the additionally + added derived value. */ + assert (runp->type < LASTREQ + && readdfcts[runp->type] != NULL); + + time_t timeout = readdfcts[runp->type] (table, runp, dh); + next_timeout = MIN (next_timeout, timeout); + + /* If the entry has been replaced, we might need + cleanup. */ + any |= !dh->usable; + } + } + } + else + { + assert (dh->usable); + next_timeout = MIN (next_timeout, dh->timeout); + } + + run = runp->next; + } + } + while (cnt > 0); + + if (__glibc_unlikely (fd != -1)) + { + /* Reply to the INVALIDATE initiator that the cache has been + invalidated. */ + int32_t resp = 0; + writeall (fd, &resp, sizeof (resp)); + } + + if (first <= last) + { + struct hashentry *head = NULL; + + /* Now we have to get the write lock since we are about to modify + the table. */ + if (__glibc_unlikely (pthread_rwlock_trywrlock (&table->lock) != 0)) + { + ++table->head->wrlockdelayed; + pthread_rwlock_wrlock (&table->lock); + } + + while (first <= last) + { + if (mark[first]) + { + ref_t *old = &table->head->array[first]; + ref_t run = table->head->array[first]; + + assert (run != ENDREF); + do + { + struct hashentry *runp = (struct hashentry *) (data + run); + struct datahead *dh + = (struct datahead *) (data + runp->packet); + + if (! dh->usable) + { + /* We need the list only for debugging but it is + more costly to avoid creating the list than + doing it. */ + runp->dellist = head; + head = runp; + + /* No need for an atomic operation, we have the + write lock. */ + --table->head->nentries; + + run = *old = runp->next; + } + else + { + old = &runp->next; + run = runp->next; + } + } + while (run != ENDREF); + } + + ++first; + } + + /* It's all done. */ + pthread_rwlock_unlock (&table->lock); + + /* Make sure the data is saved to disk. */ + if (table->persistent) + msync (table->head, + data + table->head->first_free - (char *) table->head, + MS_ASYNC); + + /* One extra pass if we do debugging. */ + if (__glibc_unlikely (debug_level > 0)) + { + struct hashentry *runp = head; + + while (runp != NULL) + { + char buf[INET6_ADDRSTRLEN]; + const char *str; + + if (runp->type == GETHOSTBYADDR || runp->type == GETHOSTBYADDRv6) + { + inet_ntop (runp->type == GETHOSTBYADDR ? AF_INET : AF_INET6, + data + runp->key, buf, sizeof (buf)); + str = buf; + } + else + str = data + runp->key; + + dbg_log ("remove %s entry \"%s\"", serv2str[runp->type], str); + + runp = runp->dellist; + } + } + } + + if (__glibc_unlikely (! mark_use_alloca)) + free (mark); + + /* Run garbage collection if any entry has been removed or replaced. */ + if (any) + gc (table); + + /* If there is no entry in the database and we therefore have no new + timeout value, tell the caller to wake up in 24 hours. */ + return next_timeout == NO_TIMEOUT ? 24 * 60 * 60 : next_timeout - now; +} diff --git a/REORG.TODO/nscd/connections.c b/REORG.TODO/nscd/connections.c new file mode 100644 index 0000000000..cc1ed72077 --- /dev/null +++ b/REORG.TODO/nscd/connections.c @@ -0,0 +1,2558 @@ +/* Inner loops of cache daemon. + Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> +#include <assert.h> +#include <atomic.h> +#include <error.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <ifaddrs.h> +#include <libintl.h> +#include <pthread.h> +#include <pwd.h> +#include <resolv.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdint.h> +#include <arpa/inet.h> +#ifdef HAVE_NETLINK +# include <linux/netlink.h> +# include <linux/rtnetlink.h> +#endif +#ifdef HAVE_EPOLL +# include <sys/epoll.h> +#endif +#ifdef HAVE_INOTIFY +# include <sys/inotify.h> +#endif +#include <sys/mman.h> +#include <sys/param.h> +#include <sys/poll.h> +#ifdef HAVE_SENDFILE +# include <sys/sendfile.h> +#endif +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> + +#include "nscd.h" +#include "dbg_log.h" +#include "selinux.h" +#include <resolv/resolv.h> + +#include <kernel-features.h> +#include <libc-diag.h> + + +/* Support to run nscd as an unprivileged user */ +const char *server_user; +static uid_t server_uid; +static gid_t server_gid; +const char *stat_user; +uid_t stat_uid; +static gid_t *server_groups; +#ifndef NGROUPS +# define NGROUPS 32 +#endif +static int server_ngroups; + +static pthread_attr_t attr; + +static void begin_drop_privileges (void); +static void finish_drop_privileges (void); + +/* Map request type to a string. */ +const char *const serv2str[LASTREQ] = +{ + [GETPWBYNAME] = "GETPWBYNAME", + [GETPWBYUID] = "GETPWBYUID", + [GETGRBYNAME] = "GETGRBYNAME", + [GETGRBYGID] = "GETGRBYGID", + [GETHOSTBYNAME] = "GETHOSTBYNAME", + [GETHOSTBYNAMEv6] = "GETHOSTBYNAMEv6", + [GETHOSTBYADDR] = "GETHOSTBYADDR", + [GETHOSTBYADDRv6] = "GETHOSTBYADDRv6", + [SHUTDOWN] = "SHUTDOWN", + [GETSTAT] = "GETSTAT", + [INVALIDATE] = "INVALIDATE", + [GETFDPW] = "GETFDPW", + [GETFDGR] = "GETFDGR", + [GETFDHST] = "GETFDHST", + [GETAI] = "GETAI", + [INITGROUPS] = "INITGROUPS", + [GETSERVBYNAME] = "GETSERVBYNAME", + [GETSERVBYPORT] = "GETSERVBYPORT", + [GETFDSERV] = "GETFDSERV", + [GETNETGRENT] = "GETNETGRENT", + [INNETGR] = "INNETGR", + [GETFDNETGR] = "GETFDNETGR" +}; + +/* The control data structures for the services. */ +struct database_dyn dbs[lastdb] = +{ + [pwddb] = { + .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP, + .prune_lock = PTHREAD_MUTEX_INITIALIZER, + .prune_run_lock = PTHREAD_MUTEX_INITIALIZER, + .enabled = 0, + .check_file = 1, + .persistent = 0, + .propagate = 1, + .shared = 0, + .max_db_size = DEFAULT_MAX_DB_SIZE, + .suggested_module = DEFAULT_SUGGESTED_MODULE, + .db_filename = _PATH_NSCD_PASSWD_DB, + .disabled_iov = &pwd_iov_disabled, + .postimeout = 3600, + .negtimeout = 20, + .wr_fd = -1, + .ro_fd = -1, + .mmap_used = false + }, + [grpdb] = { + .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP, + .prune_lock = PTHREAD_MUTEX_INITIALIZER, + .prune_run_lock = PTHREAD_MUTEX_INITIALIZER, + .enabled = 0, + .check_file = 1, + .persistent = 0, + .propagate = 1, + .shared = 0, + .max_db_size = DEFAULT_MAX_DB_SIZE, + .suggested_module = DEFAULT_SUGGESTED_MODULE, + .db_filename = _PATH_NSCD_GROUP_DB, + .disabled_iov = &grp_iov_disabled, + .postimeout = 3600, + .negtimeout = 60, + .wr_fd = -1, + .ro_fd = -1, + .mmap_used = false + }, + [hstdb] = { + .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP, + .prune_lock = PTHREAD_MUTEX_INITIALIZER, + .prune_run_lock = PTHREAD_MUTEX_INITIALIZER, + .enabled = 0, + .check_file = 1, + .persistent = 0, + .propagate = 0, /* Not used. */ + .shared = 0, + .max_db_size = DEFAULT_MAX_DB_SIZE, + .suggested_module = DEFAULT_SUGGESTED_MODULE, + .db_filename = _PATH_NSCD_HOSTS_DB, + .disabled_iov = &hst_iov_disabled, + .postimeout = 3600, + .negtimeout = 20, + .wr_fd = -1, + .ro_fd = -1, + .mmap_used = false + }, + [servdb] = { + .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP, + .prune_lock = PTHREAD_MUTEX_INITIALIZER, + .prune_run_lock = PTHREAD_MUTEX_INITIALIZER, + .enabled = 0, + .check_file = 1, + .persistent = 0, + .propagate = 0, /* Not used. */ + .shared = 0, + .max_db_size = DEFAULT_MAX_DB_SIZE, + .suggested_module = DEFAULT_SUGGESTED_MODULE, + .db_filename = _PATH_NSCD_SERVICES_DB, + .disabled_iov = &serv_iov_disabled, + .postimeout = 28800, + .negtimeout = 20, + .wr_fd = -1, + .ro_fd = -1, + .mmap_used = false + }, + [netgrdb] = { + .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP, + .prune_lock = PTHREAD_MUTEX_INITIALIZER, + .prune_run_lock = PTHREAD_MUTEX_INITIALIZER, + .enabled = 0, + .check_file = 1, + .persistent = 0, + .propagate = 0, /* Not used. */ + .shared = 0, + .max_db_size = DEFAULT_MAX_DB_SIZE, + .suggested_module = DEFAULT_SUGGESTED_MODULE, + .db_filename = _PATH_NSCD_NETGROUP_DB, + .disabled_iov = &netgroup_iov_disabled, + .postimeout = 28800, + .negtimeout = 20, + .wr_fd = -1, + .ro_fd = -1, + .mmap_used = false + } +}; + + +/* Mapping of request type to database. */ +static struct +{ + bool data_request; + struct database_dyn *db; +} const reqinfo[LASTREQ] = +{ + [GETPWBYNAME] = { true, &dbs[pwddb] }, + [GETPWBYUID] = { true, &dbs[pwddb] }, + [GETGRBYNAME] = { true, &dbs[grpdb] }, + [GETGRBYGID] = { true, &dbs[grpdb] }, + [GETHOSTBYNAME] = { true, &dbs[hstdb] }, + [GETHOSTBYNAMEv6] = { true, &dbs[hstdb] }, + [GETHOSTBYADDR] = { true, &dbs[hstdb] }, + [GETHOSTBYADDRv6] = { true, &dbs[hstdb] }, + [SHUTDOWN] = { false, NULL }, + [GETSTAT] = { false, NULL }, + [SHUTDOWN] = { false, NULL }, + [GETFDPW] = { false, &dbs[pwddb] }, + [GETFDGR] = { false, &dbs[grpdb] }, + [GETFDHST] = { false, &dbs[hstdb] }, + [GETAI] = { true, &dbs[hstdb] }, + [INITGROUPS] = { true, &dbs[grpdb] }, + [GETSERVBYNAME] = { true, &dbs[servdb] }, + [GETSERVBYPORT] = { true, &dbs[servdb] }, + [GETFDSERV] = { false, &dbs[servdb] }, + [GETNETGRENT] = { true, &dbs[netgrdb] }, + [INNETGR] = { true, &dbs[netgrdb] }, + [GETFDNETGR] = { false, &dbs[netgrdb] } +}; + + +/* 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; + +#ifdef HAVE_INOTIFY +/* Inotify descriptor. */ +int inotify_fd = -1; +#endif + +#ifdef HAVE_NETLINK +/* Descriptor for netlink status updates. */ +static int nl_status_fd = -1; +#endif + +/* Number of times clients had to wait. */ +unsigned long int client_queued; + + +ssize_t +writeall (int fd, const void *buf, size_t len) +{ + size_t n = len; + ssize_t ret; + do + { + ret = TEMP_FAILURE_RETRY (send (fd, buf, n, MSG_NOSIGNAL)); + if (ret <= 0) + break; + buf = (const char *) buf + ret; + n -= ret; + } + while (n > 0); + return ret < 0 ? ret : len - n; +} + + +#ifdef HAVE_SENDFILE +ssize_t +sendfileall (int tofd, int fromfd, off_t off, size_t len) +{ + ssize_t n = len; + ssize_t ret; + + do + { + ret = TEMP_FAILURE_RETRY (sendfile (tofd, fromfd, &off, n)); + if (ret <= 0) + break; + n -= ret; + } + while (n > 0); + return ret < 0 ? ret : len - n; +} +#endif + + +enum usekey + { + use_not = 0, + /* The following three are not really used, they are symbolic constants. */ + use_first = 16, + use_begin = 32, + use_end = 64, + + use_he = 1, + use_he_begin = use_he | use_begin, + use_he_end = use_he | use_end, + use_data = 3, + use_data_begin = use_data | use_begin, + use_data_end = use_data | use_end, + use_data_first = use_data_begin | use_first + }; + + +static int +check_use (const char *data, nscd_ssize_t first_free, uint8_t *usemap, + enum usekey use, ref_t start, size_t len) +{ + assert (len >= 2); + + if (start > first_free || start + len > first_free + || (start & BLOCK_ALIGN_M1)) + return 0; + + if (usemap[start] == use_not) + { + /* Add the start marker. */ + usemap[start] = use | use_begin; + use &= ~use_first; + + while (--len > 0) + if (usemap[++start] != use_not) + return 0; + else + usemap[start] = use; + + /* Add the end marker. */ + usemap[start] = use | use_end; + } + else if ((usemap[start] & ~use_first) == ((use | use_begin) & ~use_first)) + { + /* Hash entries can't be shared. */ + if (use == use_he) + return 0; + + usemap[start] |= (use & use_first); + use &= ~use_first; + + while (--len > 1) + if (usemap[++start] != use) + return 0; + + if (usemap[++start] != (use | use_end)) + return 0; + } + else + /* Points to a wrong object or somewhere in the middle. */ + return 0; + + return 1; +} + + +/* Verify data in persistent database. */ +static int +verify_persistent_db (void *mem, struct database_pers_head *readhead, int dbnr) +{ + assert (dbnr == pwddb || dbnr == grpdb || dbnr == hstdb || dbnr == servdb + || dbnr == netgrdb); + + time_t now = time (NULL); + + struct database_pers_head *head = mem; + struct database_pers_head head_copy = *head; + + /* Check that the header that was read matches the head in the database. */ + if (memcmp (head, readhead, sizeof (*head)) != 0) + return 0; + + /* First some easy tests: make sure the database header is sane. */ + if (head->version != DB_VERSION + || head->header_size != sizeof (*head) + /* We allow a timestamp to be one hour ahead of the current time. + This should cover daylight saving time changes. */ + || head->timestamp > now + 60 * 60 + 60 + || (head->gc_cycle & 1) + || head->module == 0 + || (size_t) head->module > INT32_MAX / sizeof (ref_t) + || (size_t) head->data_size > INT32_MAX - head->module * sizeof (ref_t) + || head->first_free < 0 + || head->first_free > head->data_size + || (head->first_free & BLOCK_ALIGN_M1) != 0 + || head->maxnentries < 0 + || head->maxnsearched < 0) + return 0; + + uint8_t *usemap = calloc (head->first_free, 1); + if (usemap == NULL) + return 0; + + const char *data = (char *) &head->array[roundup (head->module, + ALIGN / sizeof (ref_t))]; + + nscd_ssize_t he_cnt = 0; + for (nscd_ssize_t cnt = 0; cnt < head->module; ++cnt) + { + ref_t trail = head->array[cnt]; + ref_t work = trail; + int tick = 0; + + while (work != ENDREF) + { + if (! check_use (data, head->first_free, usemap, use_he, work, + sizeof (struct hashentry))) + goto fail; + + /* Now we know we can dereference the record. */ + struct hashentry *here = (struct hashentry *) (data + work); + + ++he_cnt; + + /* Make sure the record is for this type of service. */ + if (here->type >= LASTREQ + || reqinfo[here->type].db != &dbs[dbnr]) + goto fail; + + /* Validate boolean field value. */ + if (here->first != false && here->first != true) + goto fail; + + if (here->len < 0) + goto fail; + + /* Now the data. */ + if (here->packet < 0 + || here->packet > head->first_free + || here->packet + sizeof (struct datahead) > head->first_free) + goto fail; + + struct datahead *dh = (struct datahead *) (data + here->packet); + + if (! check_use (data, head->first_free, usemap, + use_data | (here->first ? use_first : 0), + here->packet, dh->allocsize)) + goto fail; + + if (dh->allocsize < sizeof (struct datahead) + || dh->recsize > dh->allocsize + || (dh->notfound != false && dh->notfound != true) + || (dh->usable != false && dh->usable != true)) + goto fail; + + if (here->key < here->packet + sizeof (struct datahead) + || here->key > here->packet + dh->allocsize + || here->key + here->len > here->packet + dh->allocsize) + goto fail; + + work = here->next; + + if (work == trail) + /* A circular list, this must not happen. */ + goto fail; + if (tick) + trail = ((struct hashentry *) (data + trail))->next; + tick = 1 - tick; + } + } + + if (he_cnt != head->nentries) + goto fail; + + /* See if all data and keys had at least one reference from + he->first == true hashentry. */ + for (ref_t idx = 0; idx < head->first_free; ++idx) + { + if (usemap[idx] == use_data_begin) + goto fail; + } + + /* Finally, make sure the database hasn't changed since the first test. */ + if (memcmp (mem, &head_copy, sizeof (*head)) != 0) + goto fail; + + free (usemap); + return 1; + +fail: + free (usemap); + return 0; +} + + +/* Initialize database information structures. */ +void +nscd_init (void) +{ + /* Look up unprivileged uid/gid/groups before we start listening on the + socket */ + if (server_user != NULL) + begin_drop_privileges (); + + if (nthreads == -1) + /* No configuration for this value, assume a default. */ + nthreads = 4; + + for (size_t cnt = 0; cnt < lastdb; ++cnt) + if (dbs[cnt].enabled) + { + pthread_rwlock_init (&dbs[cnt].lock, NULL); + pthread_mutex_init (&dbs[cnt].memlock, NULL); + + if (dbs[cnt].persistent) + { + /* Try to open the appropriate file on disk. */ + int fd = open (dbs[cnt].db_filename, O_RDWR | O_CLOEXEC); + if (fd != -1) + { + char *msg = NULL; + struct stat64 st; + void *mem; + size_t total; + struct database_pers_head head; + ssize_t n = TEMP_FAILURE_RETRY (read (fd, &head, + sizeof (head))); + if (n != sizeof (head) || fstat64 (fd, &st) != 0) + { + fail_db_errno: + /* The code is single-threaded at this point so + using strerror is just fine. */ + msg = strerror (errno); + fail_db: + dbg_log (_("invalid persistent database file \"%s\": %s"), + dbs[cnt].db_filename, msg); + unlink (dbs[cnt].db_filename); + } + else if (head.module == 0 && head.data_size == 0) + { + /* The file has been created, but the head has not + been initialized yet. */ + msg = _("uninitialized header"); + goto fail_db; + } + else if (head.header_size != (int) sizeof (head)) + { + msg = _("header size does not match"); + goto fail_db; + } + else if ((total = (sizeof (head) + + roundup (head.module * sizeof (ref_t), + ALIGN) + + head.data_size)) + > st.st_size + || total < sizeof (head)) + { + msg = _("file size does not match"); + goto fail_db; + } + /* Note we map with the maximum size allowed for the + database. This is likely much larger than the + actual file size. This is OK on most OSes since + extensions of the underlying file will + automatically translate more pages available for + memory access. */ + else if ((mem = mmap (NULL, dbs[cnt].max_db_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0)) + == MAP_FAILED) + goto fail_db_errno; + else if (!verify_persistent_db (mem, &head, cnt)) + { + munmap (mem, total); + msg = _("verification failed"); + goto fail_db; + } + else + { + /* Success. We have the database. */ + dbs[cnt].head = mem; + dbs[cnt].memsize = total; + dbs[cnt].data = (char *) + &dbs[cnt].head->array[roundup (dbs[cnt].head->module, + ALIGN / sizeof (ref_t))]; + dbs[cnt].mmap_used = true; + + if (dbs[cnt].suggested_module > head.module) + dbg_log (_("suggested size of table for database %s larger than the persistent database's table"), + dbnames[cnt]); + + dbs[cnt].wr_fd = fd; + fd = -1; + /* We also need a read-only descriptor. */ + if (dbs[cnt].shared) + { + dbs[cnt].ro_fd = open (dbs[cnt].db_filename, + O_RDONLY | O_CLOEXEC); + if (dbs[cnt].ro_fd == -1) + dbg_log (_("\ +cannot create read-only descriptor for \"%s\"; no mmap"), + dbs[cnt].db_filename); + } + + // XXX Shall we test whether the descriptors actually + // XXX point to the same file? + } + + /* Close the file descriptors in case something went + wrong in which case the variable have not been + assigned -1. */ + if (fd != -1) + close (fd); + } + else if (errno == EACCES) + do_exit (EXIT_FAILURE, 0, _("cannot access '%s'"), + dbs[cnt].db_filename); + } + + if (dbs[cnt].head == NULL) + { + /* No database loaded. Allocate the data structure, + possibly on disk. */ + struct database_pers_head head; + size_t total = (sizeof (head) + + roundup (dbs[cnt].suggested_module + * sizeof (ref_t), ALIGN) + + (dbs[cnt].suggested_module + * DEFAULT_DATASIZE_PER_BUCKET)); + + /* Try to create the database. If we do not need a + persistent database create a temporary file. */ + int fd; + int ro_fd = -1; + if (dbs[cnt].persistent) + { + fd = open (dbs[cnt].db_filename, + O_RDWR | O_CREAT | O_EXCL | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR); + if (fd != -1 && dbs[cnt].shared) + ro_fd = open (dbs[cnt].db_filename, + O_RDONLY | O_CLOEXEC); + } + else + { + char fname[] = _PATH_NSCD_XYZ_DB_TMP; + fd = mkostemp (fname, O_CLOEXEC); + + /* We do not need the file name anymore after we + opened another file descriptor in read-only mode. */ + if (fd != -1) + { + if (dbs[cnt].shared) + ro_fd = open (fname, O_RDONLY | O_CLOEXEC); + + unlink (fname); + } + } + + if (fd == -1) + { + if (errno == EEXIST) + { + dbg_log (_("database for %s corrupted or simultaneously used; remove %s manually if necessary and restart"), + dbnames[cnt], dbs[cnt].db_filename); + do_exit (1, 0, NULL); + } + + if (dbs[cnt].persistent) + dbg_log (_("cannot create %s; no persistent database used"), + dbs[cnt].db_filename); + else + dbg_log (_("cannot create %s; no sharing possible"), + dbs[cnt].db_filename); + + dbs[cnt].persistent = 0; + // XXX remember: no mmap + } + else + { + /* Tell the user if we could not create the read-only + descriptor. */ + if (ro_fd == -1 && dbs[cnt].shared) + dbg_log (_("\ +cannot create read-only descriptor for \"%s\"; no mmap"), + dbs[cnt].db_filename); + + /* Before we create the header, initialize the hash + table. That way if we get interrupted while writing + the header we can recognize a partially initialized + database. */ + size_t ps = sysconf (_SC_PAGESIZE); + char tmpbuf[ps]; + assert (~ENDREF == 0); + memset (tmpbuf, '\xff', ps); + + size_t remaining = dbs[cnt].suggested_module * sizeof (ref_t); + off_t offset = sizeof (head); + + size_t towrite; + if (offset % ps != 0) + { + towrite = MIN (remaining, ps - (offset % ps)); + if (pwrite (fd, tmpbuf, towrite, offset) != towrite) + goto write_fail; + offset += towrite; + remaining -= towrite; + } + + while (remaining > ps) + { + if (pwrite (fd, tmpbuf, ps, offset) == -1) + goto write_fail; + offset += ps; + remaining -= ps; + } + + if (remaining > 0 + && pwrite (fd, tmpbuf, remaining, offset) != remaining) + goto write_fail; + + /* Create the header of the file. */ + struct database_pers_head head = + { + .version = DB_VERSION, + .header_size = sizeof (head), + .module = dbs[cnt].suggested_module, + .data_size = (dbs[cnt].suggested_module + * DEFAULT_DATASIZE_PER_BUCKET), + .first_free = 0 + }; + void *mem; + + if ((TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head))) + != sizeof (head)) + || (TEMP_FAILURE_RETRY_VAL (posix_fallocate (fd, 0, total)) + != 0) + || (mem = mmap (NULL, dbs[cnt].max_db_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0)) == MAP_FAILED) + { + write_fail: + unlink (dbs[cnt].db_filename); + dbg_log (_("cannot write to database file %s: %s"), + dbs[cnt].db_filename, strerror (errno)); + dbs[cnt].persistent = 0; + } + else + { + /* Success. */ + dbs[cnt].head = mem; + dbs[cnt].data = (char *) + &dbs[cnt].head->array[roundup (dbs[cnt].head->module, + ALIGN / sizeof (ref_t))]; + dbs[cnt].memsize = total; + dbs[cnt].mmap_used = true; + + /* Remember the descriptors. */ + dbs[cnt].wr_fd = fd; + dbs[cnt].ro_fd = ro_fd; + fd = -1; + ro_fd = -1; + } + + if (fd != -1) + close (fd); + if (ro_fd != -1) + close (ro_fd); + } + } + + if (dbs[cnt].head == NULL) + { + /* We do not use the persistent database. Just + create an in-memory data structure. */ + assert (! dbs[cnt].persistent); + + dbs[cnt].head = xmalloc (sizeof (struct database_pers_head) + + (dbs[cnt].suggested_module + * sizeof (ref_t))); + memset (dbs[cnt].head, '\0', sizeof (struct database_pers_head)); + assert (~ENDREF == 0); + memset (dbs[cnt].head->array, '\xff', + dbs[cnt].suggested_module * sizeof (ref_t)); + dbs[cnt].head->module = dbs[cnt].suggested_module; + dbs[cnt].head->data_size = (DEFAULT_DATASIZE_PER_BUCKET + * dbs[cnt].head->module); + dbs[cnt].data = xmalloc (dbs[cnt].head->data_size); + dbs[cnt].head->first_free = 0; + + dbs[cnt].shared = 0; + assert (dbs[cnt].ro_fd == -1); + } + } + + /* Create the socket. */ + sock = socket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sock < 0) + { + dbg_log (_("cannot open socket: %s"), strerror (errno)); + do_exit (errno == EACCES ? 4 : 1, 0, NULL); + } + /* Bind a name to the socket. */ + struct sockaddr_un sock_addr; + sock_addr.sun_family = AF_UNIX; + strcpy (sock_addr.sun_path, _PATH_NSCDSOCKET); + if (bind (sock, (struct sockaddr *) &sock_addr, sizeof (sock_addr)) < 0) + { + dbg_log ("%s: %s", _PATH_NSCDSOCKET, strerror (errno)); + do_exit (errno == EACCES ? 4 : 1, 0, NULL); + } + + /* Set permissions for the socket. */ + chmod (_PATH_NSCDSOCKET, DEFFILEMODE); + + /* Set the socket up to accept connections. */ + if (listen (sock, SOMAXCONN) < 0) + { + dbg_log (_("cannot enable socket to accept connections: %s"), + strerror (errno)); + do_exit (1, 0, NULL); + } + +#ifdef HAVE_NETLINK + if (dbs[hstdb].enabled) + { + /* Try to open netlink socket to monitor network setting changes. */ + nl_status_fd = socket (AF_NETLINK, + SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + NETLINK_ROUTE); + if (nl_status_fd != -1) + { + struct sockaddr_nl snl; + memset (&snl, '\0', sizeof (snl)); + snl.nl_family = AF_NETLINK; + /* XXX Is this the best set to use? */ + snl.nl_groups = (RTMGRP_IPV4_IFADDR | RTMGRP_TC | RTMGRP_IPV4_MROUTE + | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_RULE + | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_MROUTE + | RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFINFO + | RTMGRP_IPV6_PREFIX); + + if (bind (nl_status_fd, (struct sockaddr *) &snl, sizeof (snl)) != 0) + { + close (nl_status_fd); + nl_status_fd = -1; + } + else + { + /* Start the timestamp process. */ + dbs[hstdb].head->extra_data[NSCD_HST_IDX_CONF_TIMESTAMP] + = __bump_nl_timestamp (); + } + } + } +#endif + + /* Change to unprivileged uid/gid/groups if specified in config file */ + if (server_user != NULL) + finish_drop_privileges (); +} + +#ifdef HAVE_INOTIFY +#define TRACED_FILE_MASK (IN_DELETE_SELF | IN_CLOSE_WRITE | IN_MOVE_SELF) +#define TRACED_DIR_MASK (IN_DELETE_SELF | IN_CREATE | IN_MOVED_TO | IN_MOVE_SELF) +void +install_watches (struct traced_file *finfo) +{ + /* Use inotify support if we have it. */ + if (finfo->inotify_descr[TRACED_FILE] < 0) + finfo->inotify_descr[TRACED_FILE] = inotify_add_watch (inotify_fd, + finfo->fname, + TRACED_FILE_MASK); + if (finfo->inotify_descr[TRACED_FILE] < 0) + { + dbg_log (_("disabled inotify-based monitoring for file `%s': %s"), + finfo->fname, strerror (errno)); + return; + } + dbg_log (_("monitoring file `%s` (%d)"), + finfo->fname, finfo->inotify_descr[TRACED_FILE]); + /* Additionally listen for events in the file's parent directory. + We do this because the file to be watched might be + deleted and then added back again. When it is added back again + we must re-add the watch. We must also cover IN_MOVED_TO to + detect a file being moved into the directory. */ + if (finfo->inotify_descr[TRACED_DIR] < 0) + finfo->inotify_descr[TRACED_DIR] = inotify_add_watch (inotify_fd, + finfo->dname, + TRACED_DIR_MASK); + if (finfo->inotify_descr[TRACED_DIR] < 0) + { + dbg_log (_("disabled inotify-based monitoring for directory `%s': %s"), + finfo->fname, strerror (errno)); + return; + } + dbg_log (_("monitoring directory `%s` (%d)"), + finfo->dname, finfo->inotify_descr[TRACED_DIR]); +} +#endif + +/* Register the file in FINFO as a traced file for the database DBS[DBIX]. + + We support registering multiple files per database. Each call to + register_traced_file adds to the list of registered files. + + When we prune the database, either through timeout or a request to + invalidate, we will check to see if any of the registered files has changed. + When we accept new connections to handle a cache request we will also + check to see if any of the registered files has changed. + + If we have inotify support then we install an inotify fd to notify us of + file deletion or modification, both of which will require we invalidate + the cache for the database. Without inotify support we stat the file and + store st_mtime to determine if the file has been modified. */ +void +register_traced_file (size_t dbidx, struct traced_file *finfo) +{ + /* If the database is disabled or file checking is disabled + then ignore the registration. */ + if (! dbs[dbidx].enabled || ! dbs[dbidx].check_file) + return; + + if (__glibc_unlikely (debug_level > 0)) + dbg_log (_("monitoring file %s for database %s"), + finfo->fname, dbnames[dbidx]); + +#ifdef HAVE_INOTIFY + install_watches (finfo); +#endif + struct stat64 st; + if (stat64 (finfo->fname, &st) < 0) + { + /* We cannot stat() the file. Set mtime to zero and try again later. */ + dbg_log (_("stat failed for file `%s'; will try again later: %s"), + finfo->fname, strerror (errno)); + finfo->mtime = 0; + } + else + finfo->mtime = st.st_mtime; + + /* Queue up the file name. */ + finfo->next = dbs[dbidx].traced_files; + dbs[dbidx].traced_files = finfo; +} + + +/* Close the connections. */ +void +close_sockets (void) +{ + close (sock); +} + + +static void +invalidate_cache (char *key, int fd) +{ + dbtype number; + int32_t resp; + + for (number = pwddb; number < lastdb; ++number) + if (strcmp (key, dbnames[number]) == 0) + { + struct traced_file *runp = dbs[number].traced_files; + while (runp != NULL) + { + /* Make sure we reload from file when checking mtime. */ + runp->mtime = 0; +#ifdef HAVE_INOTIFY + /* During an invalidation we try to reload the traced + file watches. This allows the user to re-sync if + inotify events were lost. Similar to what we do during + pruning. */ + install_watches (runp); +#endif + if (runp->call_res_init) + { + res_init (); + break; + } + runp = runp->next; + } + break; + } + + if (number == lastdb) + { + resp = EINVAL; + writeall (fd, &resp, sizeof (resp)); + return; + } + + if (dbs[number].enabled) + { + pthread_mutex_lock (&dbs[number].prune_run_lock); + prune_cache (&dbs[number], LONG_MAX, fd); + pthread_mutex_unlock (&dbs[number].prune_run_lock); + } + else + { + resp = 0; + writeall (fd, &resp, sizeof (resp)); + } +} + + +#ifdef SCM_RIGHTS +static void +send_ro_fd (struct database_dyn *db, char *key, int fd) +{ + /* If we do not have an read-only file descriptor do nothing. */ + if (db->ro_fd == -1) + return; + + /* We need to send some data along with the descriptor. */ + uint64_t mapsize = (db->head->data_size + + roundup (db->head->module * sizeof (ref_t), ALIGN) + + sizeof (struct database_pers_head)); + struct iovec iov[2]; + iov[0].iov_base = key; + iov[0].iov_len = strlen (key) + 1; + iov[1].iov_base = &mapsize; + iov[1].iov_len = sizeof (mapsize); + + /* Prepare the control message to transfer the descriptor. */ + union + { + struct cmsghdr hdr; + char bytes[CMSG_SPACE (sizeof (int))]; + } buf; + struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 2, + .msg_control = buf.bytes, + .msg_controllen = sizeof (buf) }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR (&msg); + + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN (sizeof (int)); + + int *ip = (int *) CMSG_DATA (cmsg); + *ip = db->ro_fd; + + msg.msg_controllen = cmsg->cmsg_len; + + /* Send the control message. We repeat when we are interrupted but + everything else is ignored. */ +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + (void) TEMP_FAILURE_RETRY (sendmsg (fd, &msg, MSG_NOSIGNAL)); + + if (__glibc_unlikely (debug_level > 0)) + dbg_log (_("provide access to FD %d, for %s"), db->ro_fd, key); +} +#endif /* SCM_RIGHTS */ + + +/* Handle new request. */ +static void +handle_request (int fd, request_header *req, void *key, uid_t uid, pid_t pid) +{ + if (__builtin_expect (req->version, NSCD_VERSION) != NSCD_VERSION) + { + if (debug_level > 0) + dbg_log (_("\ +cannot handle old request version %d; current version is %d"), + req->version, NSCD_VERSION); + return; + } + + /* Perform the SELinux check before we go on to the standard checks. */ + if (selinux_enabled && nscd_request_avc_has_perm (fd, req->type) != 0) + { + if (debug_level > 0) + { +#ifdef SO_PEERCRED +# ifdef PATH_MAX + char buf[PATH_MAX]; +# else + char buf[4096]; +# endif + + snprintf (buf, sizeof (buf), "/proc/%ld/exe", (long int) pid); + ssize_t n = readlink (buf, buf, sizeof (buf) - 1); + + if (n <= 0) + dbg_log (_("\ +request from %ld not handled due to missing permission"), (long int) pid); + else + { + buf[n] = '\0'; + dbg_log (_("\ +request from '%s' [%ld] not handled due to missing permission"), + buf, (long int) pid); + } +#else + dbg_log (_("request not handled due to missing permission")); +#endif + } + return; + } + + struct database_dyn *db = reqinfo[req->type].db; + + /* See whether we can service the request from the cache. */ + if (__builtin_expect (reqinfo[req->type].data_request, true)) + { + if (__builtin_expect (debug_level, 0) > 0) + { + if (req->type == GETHOSTBYADDR || req->type == GETHOSTBYADDRv6) + { + char buf[INET6_ADDRSTRLEN]; + + dbg_log ("\t%s (%s)", serv2str[req->type], + inet_ntop (req->type == GETHOSTBYADDR + ? AF_INET : AF_INET6, + key, buf, sizeof (buf))); + } + else + dbg_log ("\t%s (%s)", serv2str[req->type], (char *) key); + } + + /* Is this service enabled? */ + if (__glibc_unlikely (!db->enabled)) + { + /* No, sent the prepared record. */ + if (TEMP_FAILURE_RETRY (send (fd, db->disabled_iov->iov_base, + db->disabled_iov->iov_len, + MSG_NOSIGNAL)) + != (ssize_t) db->disabled_iov->iov_len + && __builtin_expect (debug_level, 0) > 0) + { + /* We have problems sending the result. */ + char buf[256]; + dbg_log (_("cannot write result: %s"), + strerror_r (errno, buf, sizeof (buf))); + } + + return; + } + + /* Be sure we can read the data. */ + if (__glibc_unlikely (pthread_rwlock_tryrdlock (&db->lock) != 0)) + { + ++db->head->rdlockdelayed; + pthread_rwlock_rdlock (&db->lock); + } + + /* See whether we can handle it from the cache. */ + struct datahead *cached; + cached = (struct datahead *) cache_search (req->type, key, req->key_len, + db, uid); + if (cached != NULL) + { + /* Hurray it's in the cache. */ + ssize_t nwritten; + +#ifdef HAVE_SENDFILE + if (__glibc_likely (db->mmap_used)) + { + assert (db->wr_fd != -1); + assert ((char *) cached->data > (char *) db->data); + assert ((char *) cached->data - (char *) db->head + + cached->recsize + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); + nwritten = sendfileall (fd, db->wr_fd, + (char *) cached->data + - (char *) db->head, cached->recsize); +# ifndef __ASSUME_SENDFILE + if (nwritten == -1 && errno == ENOSYS) + goto use_write; +# endif + } + else +# ifndef __ASSUME_SENDFILE + use_write: +# endif +#endif + nwritten = writeall (fd, cached->data, cached->recsize); + + if (nwritten != cached->recsize + && __builtin_expect (debug_level, 0) > 0) + { + /* We have problems sending the result. */ + char buf[256]; + dbg_log (_("cannot write result: %s"), + strerror_r (errno, buf, sizeof (buf))); + } + + pthread_rwlock_unlock (&db->lock); + + return; + } + + pthread_rwlock_unlock (&db->lock); + } + else if (__builtin_expect (debug_level, 0) > 0) + { + if (req->type == INVALIDATE) + dbg_log ("\t%s (%s)", serv2str[req->type], (char *) key); + else + dbg_log ("\t%s", serv2str[req->type]); + } + + /* Handle the request. */ + switch (req->type) + { + case GETPWBYNAME: + addpwbyname (db, fd, req, key, uid); + break; + + case GETPWBYUID: + addpwbyuid (db, fd, req, key, uid); + break; + + case GETGRBYNAME: + addgrbyname (db, fd, req, key, uid); + break; + + case GETGRBYGID: + addgrbygid (db, fd, req, key, uid); + break; + + case GETHOSTBYNAME: + addhstbyname (db, fd, req, key, uid); + break; + + case GETHOSTBYNAMEv6: + addhstbynamev6 (db, fd, req, key, uid); + break; + + case GETHOSTBYADDR: + addhstbyaddr (db, fd, req, key, uid); + break; + + case GETHOSTBYADDRv6: + addhstbyaddrv6 (db, fd, req, key, uid); + break; + + case GETAI: + addhstai (db, fd, req, key, uid); + break; + + case INITGROUPS: + addinitgroups (db, fd, req, key, uid); + break; + + case GETSERVBYNAME: + addservbyname (db, fd, req, key, uid); + break; + + case GETSERVBYPORT: + addservbyport (db, fd, req, key, uid); + break; + + case GETNETGRENT: + addgetnetgrent (db, fd, req, key, uid); + break; + + case INNETGR: + addinnetgr (db, fd, req, key, uid); + break; + + case GETSTAT: + case SHUTDOWN: + case INVALIDATE: + { + /* Get the callers credentials. */ +#ifdef SO_PEERCRED + struct ucred caller; + socklen_t optlen = sizeof (caller); + + if (getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &caller, &optlen) < 0) + { + char buf[256]; + + dbg_log (_("error getting caller's id: %s"), + strerror_r (errno, buf, sizeof (buf))); + break; + } + + uid = caller.uid; +#else + /* Some systems have no SO_PEERCRED implementation. They don't + care about security so we don't as well. */ + uid = 0; +#endif + } + + /* Accept shutdown, getstat and invalidate only from root. For + the stat call also allow the user specified in the config file. */ + if (req->type == GETSTAT) + { + if (uid == 0 || uid == stat_uid) + send_stats (fd, dbs); + } + else if (uid == 0) + { + if (req->type == INVALIDATE) + invalidate_cache (key, fd); + else + termination_handler (0); + } + break; + + case GETFDPW: + case GETFDGR: + case GETFDHST: + case GETFDSERV: + case GETFDNETGR: +#ifdef SCM_RIGHTS + send_ro_fd (reqinfo[req->type].db, key, fd); +#endif + break; + + default: + /* Ignore the command, it's nothing we know. */ + break; + } +} + + +/* 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 read /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 (setresuid (old_uid, old_uid, old_uid) != 0) + { + dbg_log (_("\ +cannot change to old UID: %s; disabling paranoia mode"), + strerror (errno)); + + paranoia = 0; + return; + } + + if (setresgid (old_gid, old_gid, old_gid) != 0) + { + dbg_log (_("\ +cannot change to old GID: %s; disabling paranoia mode"), + strerror (errno)); + + ignore_value (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) + { + ignore_value (setuid (server_uid)); + ignore_value (setgid (server_gid)); + } + paranoia = 0; + return; + } + + /* Synchronize memory. */ + int32_t certainly[lastdb]; + for (int cnt = 0; cnt < lastdb; ++cnt) + if (dbs[cnt].enabled) + { + /* Make sure nobody keeps using the database. */ + dbs[cnt].head->timestamp = 0; + certainly[cnt] = dbs[cnt].head->nscd_certainly_running; + dbs[cnt].head->nscd_certainly_running = 0; + + if (dbs[cnt].persistent) + // XXX async OK? + msync (dbs[cnt].head, dbs[cnt].memsize, MS_ASYNC); + } + + /* The preparations are done. */ +#ifdef PATH_MAX + char pathbuf[PATH_MAX]; +#else + char pathbuf[256]; +#endif + /* Try to exec the real nscd program so the process name (as reported + in /proc/PID/status) will be 'nscd', but fall back to /proc/self/exe + if readlink or the exec with the result of the readlink call fails. */ + ssize_t n = readlink ("/proc/self/exe", pathbuf, sizeof (pathbuf) - 1); + if (n != -1) + { + pathbuf[n] = '\0'; + execv (pathbuf, argv); + } + 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) + { + ignore_value (setuid (server_uid)); + ignore_value (setgid (server_gid)); + } + if (chdir ("/") != 0) + dbg_log (_("cannot change current working directory to \"/\": %s"), + strerror (errno)); + paranoia = 0; + + /* Reenable the databases. */ + time_t now = time (NULL); + for (int cnt = 0; cnt < lastdb; ++cnt) + if (dbs[cnt].enabled) + { + dbs[cnt].head->timestamp = now; + dbs[cnt].head->nscd_certainly_running = certainly[cnt]; + } +} + + +/* 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 = PTHREAD_COND_INITIALIZER; +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; + + +/* Function for the clean-up threads. */ +static void * +__attribute__ ((__noreturn__)) +nscd_run_prune (void *p) +{ + const long int my_number = (long int) p; + assert (dbs[my_number].enabled); + + int dont_need_update = setup_thread (&dbs[my_number]); + + time_t now = time (NULL); + + /* We are running. */ + dbs[my_number].head->timestamp = now; + + struct timespec prune_ts; + if (__glibc_unlikely (clock_gettime (timeout_clock, &prune_ts) == -1)) + /* Should never happen. */ + abort (); + + /* Compute the initial timeout time. Prevent all the timers to go + off at the same time by adding a db-based value. */ + prune_ts.tv_sec += CACHE_PRUNE_INTERVAL + my_number; + dbs[my_number].wakeup_time = now + CACHE_PRUNE_INTERVAL + my_number; + + pthread_mutex_t *prune_lock = &dbs[my_number].prune_lock; + pthread_mutex_t *prune_run_lock = &dbs[my_number].prune_run_lock; + pthread_cond_t *prune_cond = &dbs[my_number].prune_cond; + + pthread_mutex_lock (prune_lock); + while (1) + { + /* Wait, but not forever. */ + int e = 0; + if (! dbs[my_number].clear_cache) + e = pthread_cond_timedwait (prune_cond, prune_lock, &prune_ts); + assert (__builtin_expect (e == 0 || e == ETIMEDOUT, 1)); + + time_t next_wait; + now = time (NULL); + if (e == ETIMEDOUT || now >= dbs[my_number].wakeup_time + || dbs[my_number].clear_cache) + { + /* We will determine the new timout values based on the + cache content. Should there be concurrent additions to + the cache which are not accounted for in the cache + pruning we want to know about it. Therefore set the + timeout to the maximum. It will be descreased when adding + new entries to the cache, if necessary. */ + dbs[my_number].wakeup_time = MAX_TIMEOUT_VALUE; + + /* Unconditionally reset the flag. */ + time_t prune_now = dbs[my_number].clear_cache ? LONG_MAX : now; + dbs[my_number].clear_cache = 0; + + pthread_mutex_unlock (prune_lock); + + /* We use a separate lock for running the prune function (instead + of keeping prune_lock locked) because this enables concurrent + invocations of cache_add which might modify the timeout value. */ + pthread_mutex_lock (prune_run_lock); + next_wait = prune_cache (&dbs[my_number], prune_now, -1); + pthread_mutex_unlock (prune_run_lock); + + next_wait = MAX (next_wait, CACHE_PRUNE_INTERVAL); + /* If clients cannot determine for sure whether nscd is running + we need to wake up occasionally to update the timestamp. + Wait 90% of the update period. */ +#define UPDATE_MAPPING_TIMEOUT (MAPPING_TIMEOUT * 9 / 10) + if (__glibc_unlikely (! dont_need_update)) + { + next_wait = MIN (UPDATE_MAPPING_TIMEOUT, next_wait); + dbs[my_number].head->timestamp = now; + } + + pthread_mutex_lock (prune_lock); + + /* Make it known when we will wake up again. */ + if (now + next_wait < dbs[my_number].wakeup_time) + dbs[my_number].wakeup_time = now + next_wait; + else + next_wait = dbs[my_number].wakeup_time - now; + } + else + /* The cache was just pruned. Do not do it again now. Just + use the new timeout value. */ + next_wait = dbs[my_number].wakeup_time - now; + + if (clock_gettime (timeout_clock, &prune_ts) == -1) + /* Should never happen. */ + abort (); + + /* Compute next timeout time. */ + prune_ts.tv_sec += next_wait; + } +} + + +/* This is the main loop. It is replicated in different threads but + the use of the ready list makes sure only one thread handles an + incoming connection. */ +static void * +__attribute__ ((__noreturn__)) +nscd_run_worker (void *p) +{ + char buf[256]; + + /* Initial locking. */ + pthread_mutex_lock (&readylist_lock); + + /* One more thread available. */ + ++nready; + + while (1) + { + while (readylist == NULL) + pthread_cond_wait (&readylist_cond, &readylist_lock); + + struct fdlist *it = readylist->next; + if (readylist->next == readylist) + /* Just one entry on the list. */ + readylist = NULL; + else + readylist->next = it->next; + + /* Extract the information and mark the record ready to be used + again. */ + int fd = it->fd; + it->next = NULL; + + /* One more thread available. */ + --nready; + + /* We are done with the list. */ + pthread_mutex_unlock (&readylist_lock); + + /* 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))); + goto close_and_out; + } + + /* Check whether this is a valid request type. */ + if (req.type < GETPWBYNAME || req.type >= LASTREQ) + goto close_and_out; + + /* 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 (__glibc_unlikely (debug_level > 0)) + { + struct ucred caller; + socklen_t optlen = sizeof (caller); + + if (getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &caller, &optlen) == 0) + pid = caller.pid; + } +#else + const pid_t pid = 0; +#endif + + /* 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. */ + if (__builtin_expect (req.key_len, 1) < 0 + || __builtin_expect (req.key_len, 1) > MAXKEYLEN) + { + if (debug_level > 0) + dbg_log (_("key length in request too long: %d"), req.key_len); + } + else + { + /* Get the key. */ + char keybuf[MAXKEYLEN + 1]; + + 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))); + goto close_and_out; + } + keybuf[req.key_len] = '\0'; + + if (__builtin_expect (debug_level, 0) > 0) + { +#ifdef SO_PEERCRED + if (pid != 0) + dbg_log (_("\ +handle_request: request received (Version = %d) from PID %ld"), + req.version, (long int) pid); + else +#endif + dbg_log (_("\ +handle_request: request received (Version = %d)"), req.version); + } + + /* Phew, we got all the data, now process it. */ + handle_request (fd, &req, keybuf, uid, pid); + } + + close_and_out: + /* We are done. */ + close (fd); + + /* Re-locking. */ + pthread_mutex_lock (&readylist_lock); + + /* One more thread available. */ + ++nready; + } + /* NOTREACHED */ +} + + +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); + + 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 (__glibc_unlikely (nready == 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_worker, + (void *) (long int) nthreads) == 0) + { + /* We got another thread. */ + ++nthreads; + /* The new thread might need 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 bool +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; + +#ifdef HAVE_INOTIFY +/* Inotify event for changed file. */ +union __inev +{ + struct inotify_event i; +# ifndef PATH_MAX +# define PATH_MAX 1024 +# endif + char buf[sizeof (struct inotify_event) + PATH_MAX]; +}; + +/* Returns 0 if the file is there otherwise -1. */ +int +check_file (struct traced_file *finfo) +{ + struct stat64 st; + /* We could check mtime and if different re-add + the watches, and invalidate the database, but we + don't because we are called from inotify_check_files + which should be doing that work. If sufficient inotify + events were lost then the next pruning or invalidation + will do the stat and mtime check. We don't do it here to + keep the logic simple. */ + if (stat64 (finfo->fname, &st) < 0) + return -1; + return 0; +} + +/* Process the inotify event in INEV. If the event matches any of the files + registered with a database then mark that database as requiring its cache + to be cleared. We indicate the cache needs clearing by setting + TO_CLEAR[DBCNT] to true for the matching database. */ +static void +inotify_check_files (bool *to_clear, union __inev *inev) +{ + /* Check which of the files changed. */ + for (size_t dbcnt = 0; dbcnt < lastdb; ++dbcnt) + { + struct traced_file *finfo = dbs[dbcnt].traced_files; + + while (finfo != NULL) + { + /* The configuration file was moved or deleted. + We stop watching it at that point, and reinitialize. */ + if (finfo->inotify_descr[TRACED_FILE] == inev->i.wd + && ((inev->i.mask & IN_MOVE_SELF) + || (inev->i.mask & IN_DELETE_SELF) + || (inev->i.mask & IN_IGNORED))) + { + int ret; + bool moved = (inev->i.mask & IN_MOVE_SELF) != 0; + + if (check_file (finfo) == 0) + { + dbg_log (_("ignored inotify event for `%s` (file exists)"), + finfo->fname); + return; + } + + dbg_log (_("monitored file `%s` was %s, removing watch"), + finfo->fname, moved ? "moved" : "deleted"); + /* File was moved out, remove the watch. Watches are + automatically removed when the file is deleted. */ + if (moved) + { + ret = inotify_rm_watch (inotify_fd, inev->i.wd); + if (ret < 0) + dbg_log (_("failed to remove file watch `%s`: %s"), + finfo->fname, strerror (errno)); + } + finfo->inotify_descr[TRACED_FILE] = -1; + to_clear[dbcnt] = true; + if (finfo->call_res_init) + res_init (); + return; + } + /* The configuration file was open for writing and has just closed. + We reset the cache and reinitialize. */ + if (finfo->inotify_descr[TRACED_FILE] == inev->i.wd + && inev->i.mask & IN_CLOSE_WRITE) + { + /* Mark cache as needing to be cleared and reinitialize. */ + dbg_log (_("monitored file `%s` was written to"), finfo->fname); + to_clear[dbcnt] = true; + if (finfo->call_res_init) + res_init (); + return; + } + /* The parent directory was moved or deleted. We trigger one last + invalidation. At the next pruning or invalidation we may add + this watch back if the file is present again. */ + if (finfo->inotify_descr[TRACED_DIR] == inev->i.wd + && ((inev->i.mask & IN_DELETE_SELF) + || (inev->i.mask & IN_MOVE_SELF) + || (inev->i.mask & IN_IGNORED))) + { + bool moved = (inev->i.mask & IN_MOVE_SELF) != 0; + /* The directory watch may have already been removed + but we don't know so we just remove it again and + ignore the error. Then we remove the file watch. + Note: watches are automatically removed for deleted + files. */ + if (moved) + inotify_rm_watch (inotify_fd, inev->i.wd); + if (finfo->inotify_descr[TRACED_FILE] != -1) + { + dbg_log (_("monitored parent directory `%s` was %s, removing watch on `%s`"), + finfo->dname, moved ? "moved" : "deleted", finfo->fname); + if (inotify_rm_watch (inotify_fd, finfo->inotify_descr[TRACED_FILE]) < 0) + dbg_log (_("failed to remove file watch `%s`: %s"), + finfo->dname, strerror (errno)); + } + finfo->inotify_descr[TRACED_FILE] = -1; + finfo->inotify_descr[TRACED_DIR] = -1; + to_clear[dbcnt] = true; + if (finfo->call_res_init) + res_init (); + /* Continue to the next entry since this might be the + parent directory for multiple registered files and + we want to remove watches for all registered files. */ + continue; + } + /* The parent directory had a create or moved to event. */ + if (finfo->inotify_descr[TRACED_DIR] == inev->i.wd + && ((inev->i.mask & IN_MOVED_TO) + || (inev->i.mask & IN_CREATE)) + && strcmp (inev->i.name, finfo->sfname) == 0) + { + /* We detected a directory change. We look for the creation + of the file we are tracking or the move of the same file + into the directory. */ + int ret; + dbg_log (_("monitored file `%s` was %s, adding watch"), + finfo->fname, + inev->i.mask & IN_CREATE ? "created" : "moved into place"); + /* File was moved in or created. Regenerate the watch. */ + if (finfo->inotify_descr[TRACED_FILE] != -1) + inotify_rm_watch (inotify_fd, + finfo->inotify_descr[TRACED_FILE]); + + ret = inotify_add_watch (inotify_fd, + finfo->fname, + TRACED_FILE_MASK); + if (ret < 0) + dbg_log (_("failed to add file watch `%s`: %s"), + finfo->fname, strerror (errno)); + + finfo->inotify_descr[TRACED_FILE] = ret; + + /* The file is new or moved so mark cache as needing to + be cleared and reinitialize. */ + to_clear[dbcnt] = true; + if (finfo->call_res_init) + res_init (); + + /* Done re-adding the watch. Don't return, we may still + have other files in this same directory, same watch + descriptor, and need to process them. */ + } + /* Other events are ignored, and we move on to the next file. */ + finfo = finfo->next; + } + } +} + +/* If an entry in the array of booleans TO_CLEAR is TRUE then clear the cache + for the associated database, otherwise do nothing. The TO_CLEAR array must + have LASTDB entries. */ +static inline void +clear_db_cache (bool *to_clear) +{ + for (size_t dbcnt = 0; dbcnt < lastdb; ++dbcnt) + if (to_clear[dbcnt]) + { + pthread_mutex_lock (&dbs[dbcnt].prune_lock); + dbs[dbcnt].clear_cache = 1; + pthread_mutex_unlock (&dbs[dbcnt].prune_lock); + pthread_cond_signal (&dbs[dbcnt].prune_cond); + } +} + +int +handle_inotify_events (void) +{ + bool to_clear[lastdb] = { false, }; + union __inev inev; + + /* Read all inotify events for files registered via + register_traced_file(). */ + while (1) + { + /* Potentially read multiple events into buf. */ + ssize_t nb = TEMP_FAILURE_RETRY (read (inotify_fd, + &inev.buf, + sizeof (inev))); + if (nb < (ssize_t) sizeof (struct inotify_event)) + { + /* Not even 1 event. */ + if (__glibc_unlikely (nb == -1 && errno != EAGAIN)) + return -1; + /* Done reading events that are ready. */ + break; + } + /* Process all events. The normal inotify interface delivers + complete events on a read and never a partial event. */ + char *eptr = &inev.buf[0]; + ssize_t count; + while (1) + { + /* Check which of the files changed. */ + inotify_check_files (to_clear, &inev); + count = sizeof (struct inotify_event) + inev.i.len; + eptr += count; + nb -= count; + if (nb >= (ssize_t) sizeof (struct inotify_event)) + memcpy (&inev, eptr, nb); + else + break; + } + continue; + } + /* Actually perform the cache clearing. */ + clear_db_cache (to_clear); + return 0; +} + +#endif + +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; + +#ifdef HAVE_INOTIFY + if (inotify_fd != -1) + { + conns[1].fd = inotify_fd; + conns[1].events = POLLRDNORM; + nused = 2; + firstfree = 2; + } +#endif + +#ifdef HAVE_NETLINK + size_t idx_nl_status_fd = 0; + if (nl_status_fd != -1) + { + idx_nl_status_fd = nused; + conns[nused].fd = nl_status_fd; + conns[nused].events = POLLRDNORM; + ++nused; + firstfree = nused; + } +#endif + + 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 (accept4 (sock, NULL, NULL, + SOCK_NONBLOCK)); + + /* Use the descriptor if we have not reached the limit. */ + if (fd >= 0) + { + if (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); + } + else + /* We cannot use the connection so close it. */ + close (fd); + } + + --n; + } + + size_t first = 1; +#ifdef HAVE_INOTIFY + if (inotify_fd != -1 && conns[1].fd == inotify_fd) + { + if (conns[1].revents != 0) + { + int ret; + ret = handle_inotify_events (); + if (ret == -1) + { + /* Something went wrong when reading the inotify + data. Better disable inotify. */ + dbg_log (_("disabled inotify-based monitoring after read error %d"), errno); + conns[1].fd = -1; + firstfree = 1; + if (nused == 2) + nused = 1; + close (inotify_fd); + inotify_fd = -1; + } + --n; + } + + first = 2; + } +#endif + +#ifdef HAVE_NETLINK + if (idx_nl_status_fd != 0 && conns[idx_nl_status_fd].revents != 0) + { + char buf[4096]; + /* Read all the data. We do not interpret it here. */ + while (TEMP_FAILURE_RETRY (read (nl_status_fd, buf, + sizeof (buf))) != -1) + ; + + dbs[hstdb].head->extra_data[NSCD_HST_IDX_CONF_TIMESTAMP] + = __bump_nl_timestamp (); + } +#endif + + for (size_t cnt = first; 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; + +# ifdef HAVE_INOTIFY + if (inotify_fd != -1) + { + ev.events = EPOLLRDNORM; + ev.data.fd = inotify_fd; + if (epoll_ctl (efd, EPOLL_CTL_ADD, inotify_fd, &ev) == -1) + /* We cannot use epoll. */ + return; + nused = 2; + } +# endif + +# ifdef HAVE_NETLINK + if (nl_status_fd != -1) + { + ev.events = EPOLLRDNORM; + ev.data.fd = nl_status_fd; + if (epoll_ctl (efd, EPOLL_CTL_ADD, nl_status_fd, &ev) == -1) + /* We cannot use epoll. */ + return; + } +# endif + + 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 (accept4 (sock, NULL, NULL, + SOCK_NONBLOCK)); + + /* Use the descriptor if we have not reached the limit. */ + 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; + } + } + } +# ifdef HAVE_INOTIFY + else if (revs[cnt].data.fd == inotify_fd) + { + int ret; + ret = handle_inotify_events (); + if (ret == -1) + { + /* Something went wrong when reading the inotify + data. Better disable inotify. */ + dbg_log (_("disabled inotify-based monitoring after read error %d"), errno); + (void) epoll_ctl (efd, EPOLL_CTL_DEL, inotify_fd, NULL); + close (inotify_fd); + inotify_fd = -1; + break; + } + } +# endif +# ifdef HAVE_NETLINK + else if (revs[cnt].data.fd == nl_status_fd) + { + char buf[4096]; + /* Read all the data. We do not interpret it here. */ + while (TEMP_FAILURE_RETRY (read (nl_status_fd, buf, + sizeof (buf))) != -1) + ; + + __bump_nl_timestamp (); + } +# endif + else + { + /* Remove the descriptor from the epoll descriptor. */ + (void) epoll_ctl (efd, EPOLL_CTL_DEL, revs[cnt].data.fd, NULL); + + /* Get a worker 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; + assert (starttime[sock] == 0); +# ifdef HAVE_INOTIFY + assert (inotify_fd == -1 || starttime[inotify_fd] == 0); +# endif + assert (nl_status_fd == -1 || starttime[nl_status_fd] == 0); + for (int cnt = highest; cnt > STDERR_FILENO; --cnt) + if (starttime[cnt] != 0 && starttime[cnt] < laststart) + { + /* We are waiting for this one for too long. Close it. */ + (void) epoll_ctl (efd, EPOLL_CTL_DEL, cnt, NULL); + + (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) +{ + /* 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 defined _POSIX_CLOCK_SELECTION && _POSIX_CLOCK_SELECTION >= 0 \ + && defined _POSIX_MONOTONIC_CLOCK && _POSIX_MONOTONIC_CLOCK >= 0 + /* Determine whether the monotonous clock is available. */ + struct timespec dummy; +# if _POSIX_MONOTONIC_CLOCK == 0 + if (sysconf (_SC_MONOTONIC_CLOCK) > 0) +# endif +# if _POSIX_CLOCK_SELECTION == 0 + if (sysconf (_SC_CLOCK_SELECTION) > 0) +# endif + if (clock_getres (CLOCK_MONOTONIC, &dummy) == 0 + && pthread_condattr_setclock (&condattr, CLOCK_MONOTONIC) == 0) + timeout_clock = CLOCK_MONOTONIC; +#endif + + /* 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, NSCD_THREAD_STACKSIZE); + + /* We allow less than LASTDB threads only for debugging. */ + if (debug_level == 0) + nthreads = MAX (nthreads, lastdb); + + /* Create the threads which prune the databases. */ + // XXX Ideally this work would be done by some of the worker threads. + // XXX But this is problematic since we would need to be able to wake + // XXX them up explicitly as well as part of the group handling the + // XXX ready-list. This requires an operation where we can wait on + // XXX two conditional variables at the same time. This operation + // XXX does not exist (yet). + for (long int i = 0; i < lastdb; ++i) + { + /* Initialize the conditional variable. */ + if (pthread_cond_init (&dbs[i].prune_cond, &condattr) != 0) + { + dbg_log (_("could not initialize conditional variable")); + do_exit (1, 0, NULL); + } + + pthread_t th; + if (dbs[i].enabled + && pthread_create (&th, &attr, nscd_run_prune, (void *) i) != 0) + { + dbg_log (_("could not start clean-up thread; terminating")); + do_exit (1, 0, NULL); + } + } + + pthread_condattr_destroy (&condattr); + + for (long int i = 0; i < nthreads; ++i) + { + pthread_t th; + if (pthread_create (&th, &attr, nscd_run_worker, NULL) != 0) + { + if (i == 0) + { + dbg_log (_("could not start any worker thread; terminating")); + do_exit (1, 0, NULL); + } + + break; + } + } + + /* Now it is safe to let the parent know that we're doing fine and it can + exit. */ + notify_parent (0); + + /* 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 + + main_loop_poll (); +} + + +/* Look up the uid, gid, and supplementary groups to run nscd as. When + this function is called, we are not listening on the nscd socket yet so + we can just use the ordinary lookup functions without causing a lockup */ +static void +begin_drop_privileges (void) +{ + struct passwd *pwd = getpwnam (server_user); + + if (pwd == NULL) + { + dbg_log (_("Failed to run nscd as user '%s'"), server_user); + do_exit (EXIT_FAILURE, 0, + _("Failed to run nscd as user '%s'"), server_user); + } + + 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. */ + dbg_log (_("Failed to run nscd as user '%s'"), server_user); + do_exit (EXIT_FAILURE, errno, + _("initial getgrouplist failed")); + } + + server_groups = (gid_t *) xmalloc (server_ngroups * sizeof (gid_t)); + + if (getgrouplist (server_user, server_gid, server_groups, &server_ngroups) + == -1) + { + dbg_log (_("Failed to run nscd as user '%s'"), server_user); + do_exit (EXIT_FAILURE, errno, _("getgrouplist failed")); + } +} + + +/* Call setgroups(), setgid(), and setuid() to drop root privileges and + run nscd as the user specified in the configuration file. */ +static void +finish_drop_privileges (void) +{ +#if defined HAVE_LIBAUDIT && defined HAVE_LIBCAP + /* We need to preserve the capabilities to connect to the audit daemon. */ + cap_t new_caps = preserve_capabilities (); +#endif + + if (setgroups (server_ngroups, server_groups) == -1) + { + dbg_log (_("Failed to run nscd as user '%s'"), server_user); + do_exit (EXIT_FAILURE, errno, _("setgroups failed")); + } + + int res; + if (paranoia) + res = setresgid (server_gid, server_gid, old_gid); + else + res = setgid (server_gid); + if (res == -1) + { + dbg_log (_("Failed to run nscd as user '%s'"), server_user); + do_exit (4, errno, "setgid"); + } + + if (paranoia) + res = setresuid (server_uid, server_uid, old_uid); + else + res = setuid (server_uid); + if (res == -1) + { + dbg_log (_("Failed to run nscd as user '%s'"), server_user); + do_exit (4, errno, "setuid"); + } + +#if defined HAVE_LIBAUDIT && defined HAVE_LIBCAP + /* Remove the temporary capabilities. */ + install_real_capabilities (new_caps); +#endif +} diff --git a/REORG.TODO/nscd/dbg_log.c b/REORG.TODO/nscd/dbg_log.c new file mode 100644 index 0000000000..d4b19acc0c --- /dev/null +++ b/REORG.TODO/nscd/dbg_log.c @@ -0,0 +1,85 @@ +/* Copyright (c) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include "dbg_log.h" +#include "nscd.h" + +/* if in debug mode and we have a debug file, we write the messages to it, + if in debug mode and no debug file, we write the messages to stderr, + else to syslog. */ + +static char *logfilename; +FILE *dbgout; +int debug_level; + +void +set_logfile (const char *logfile) +{ + logfilename = strdup (logfile); +} + +int +init_logfile (void) +{ + if (logfilename) + { + dbgout = fopen64 (logfilename, "a"); + return dbgout == NULL ? 0 : 1; + } + return 1; +} + +void +dbg_log (const char *fmt,...) +{ + va_list ap; + char msg2[512]; + + va_start (ap, fmt); + vsnprintf (msg2, sizeof (msg2), fmt, ap); + + if (debug_level > 0) + { + time_t t = time (NULL); + + struct tm now; + localtime_r (&t, &now); + + char buf[256]; + strftime (buf, sizeof (buf), "%c", &now); + + char msg[512]; + snprintf (msg, sizeof (msg), "%s - %d: %s%s", buf, getpid (), msg2, + msg2[strlen (msg2) - 1] == '\n' ? "" : "\n"); + if (dbgout) + { + fputs (msg, dbgout); + fflush (dbgout); + } + else + fputs (msg, stderr); + } + else + syslog (LOG_NOTICE, "%d %s", getpid (), msg2); + + va_end (ap); +} diff --git a/REORG.TODO/nscd/dbg_log.h b/REORG.TODO/nscd/dbg_log.h new file mode 100644 index 0000000000..158bfb39aa --- /dev/null +++ b/REORG.TODO/nscd/dbg_log.h @@ -0,0 +1,30 @@ +/* Copyright (c) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#ifndef _DBG_LOG_H +#define _DBG_LOG_H 1 + +extern int debug_level; + +extern void dbg_log (const char *str, ...) + __attribute__ ((__format__ (__printf__, 1, 2)));; + +extern void set_logfile (const char *logfile); +extern int init_logfile (void); + +#endif diff --git a/REORG.TODO/nscd/gai.c b/REORG.TODO/nscd/gai.c new file mode 100644 index 0000000000..a1aeadadc3 --- /dev/null +++ b/REORG.TODO/nscd/gai.c @@ -0,0 +1,48 @@ +/* Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 2004. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> + +/* This file uses the getaddrinfo code but it compiles it without NSCD + support. We just need a few symbol renames. */ +#define __inet_aton inet_aton +#define __ioctl ioctl +#define __getsockname getsockname +#define __socket socket +#define __recvmsg recvmsg +#define __bind bind +#define __sendto sendto +#define __strchrnul strchrnul +#define __getline getline +#define __qsort_r qsort_r +/* nscd uses 1MB or 2MB thread stacks. */ +#define __libc_use_alloca(size) (size <= __MAX_ALLOCA_CUTOFF) + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include <getaddrinfo.c> + +/* Support code. */ +#include <check_pf.c> +#include <check_native.c> +#ifdef HAVE_LIBIDN +# include <libidn/idn-stub.c> +#endif + +/* Some variables normally defined in libc. */ +service_user *__nss_hosts_database; diff --git a/REORG.TODO/nscd/getgrgid_r.c b/REORG.TODO/nscd/getgrgid_r.c new file mode 100644 index 0000000000..0e7fd21be7 --- /dev/null +++ b/REORG.TODO/nscd/getgrgid_r.c @@ -0,0 +1,35 @@ +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <grp.h> + +#include <grp-merge.h> + +#define LOOKUP_TYPE struct group +#define FUNCTION_NAME getgrgid +#define DATABASE_NAME group +#define ADD_PARAMS gid_t gid +#define ADD_VARIABLES gid +#define BUFLEN NSS_BUFLEN_GROUP + +#define DEEPCOPY_FN __copy_grp +#define MERGE_FN __merge_grp + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include <nss/getXXbyYY_r.c> diff --git a/REORG.TODO/nscd/getgrnam_r.c b/REORG.TODO/nscd/getgrnam_r.c new file mode 100644 index 0000000000..80cb441888 --- /dev/null +++ b/REORG.TODO/nscd/getgrnam_r.c @@ -0,0 +1,34 @@ +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <grp.h> + +#include <grp-merge.h> + +#define LOOKUP_TYPE struct group +#define FUNCTION_NAME getgrnam +#define DATABASE_NAME group +#define ADD_PARAMS const char *name +#define ADD_VARIABLES name + +#define DEEPCOPY_FN __copy_grp +#define MERGE_FN __merge_grp + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include <nss/getXXbyYY_r.c> diff --git a/REORG.TODO/nscd/gethstbyad_r.c b/REORG.TODO/nscd/gethstbyad_r.c new file mode 100644 index 0000000000..b17f0d2b51 --- /dev/null +++ b/REORG.TODO/nscd/gethstbyad_r.c @@ -0,0 +1,46 @@ +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <netdb.h> + + +#define LOOKUP_TYPE struct hostent +#define FUNCTION_NAME gethostbyaddr2 +#define FUNCTION2_NAME gethostbyaddr +#define DATABASE_NAME hosts +#define ADD_PARAMS const void *addr, socklen_t len, int type +#define EXTRA_PARAMS , int32_t *ttlp +#define ADD_VARIABLES addr, len, type +#define EXTRA_VARIABLES , ttlp +#define NEED_H_ERRNO 1 +#define NEED__RES 1 +#define NEED__RES_HCONF 1 + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include "../nss/getXXbyYY_r.c" + + +int +__gethostbyaddr_r (const void *addr, socklen_t len, int type, + struct hostent *result_buf, char *buf, size_t buflen, + struct hostent **result, int *h_errnop) +{ + return __gethostbyaddr2_r (addr, len, type, result_buf, buf, buflen, + result, h_errnop, NULL); +} diff --git a/REORG.TODO/nscd/gethstbynm3_r.c b/REORG.TODO/nscd/gethstbynm3_r.c new file mode 100644 index 0000000000..41bb26845d --- /dev/null +++ b/REORG.TODO/nscd/gethstbynm3_r.c @@ -0,0 +1,55 @@ +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <ctype.h> +#include <errno.h> +#include <netdb.h> +#include <string.h> +#include <arpa/inet.h> +#include <netinet/in.h> + + +#define LOOKUP_TYPE struct hostent +#define FUNCTION_NAME gethostbyname3 +#define FUNCTION2_NAME gethostbyname2 +#define DATABASE_NAME hosts +#define ADD_PARAMS const char *name, int af +#define EXTRA_PARAMS , int32_t *ttlp, char **canonp +#define ADD_VARIABLES name, af +#define EXTRA_VARIABLES , ttlp, canonp +#define NEED_H_ERRNO 1 +#define NEED__RES_HCONF 1 + +#define HANDLE_DIGITS_DOTS 1 +#define HAVE_LOOKUP_BUFFER 1 +#define HAVE_AF 1 + +#define __inet_aton inet_aton + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include "../nss/getXXbyYY_r.c" + + +int +__gethostbyname2_r (const char *name, int af, struct hostent *ret, char *buf, + size_t buflen, struct hostent **result, int *h_errnop) +{ + return __gethostbyname3_r (name, af, ret, buf, buflen, result, h_errnop, + NULL, NULL); +} diff --git a/REORG.TODO/nscd/getpwnam_r.c b/REORG.TODO/nscd/getpwnam_r.c new file mode 100644 index 0000000000..9af95b6c01 --- /dev/null +++ b/REORG.TODO/nscd/getpwnam_r.c @@ -0,0 +1,31 @@ +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <pwd.h> + + +#define LOOKUP_TYPE struct passwd +#define FUNCTION_NAME getpwnam +#define DATABASE_NAME passwd +#define ADD_PARAMS const char *name +#define ADD_VARIABLES name +#define BUFLEN NSS_BUFLEN_PASSWD + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include <nss/getXXbyYY_r.c> diff --git a/REORG.TODO/nscd/getpwuid_r.c b/REORG.TODO/nscd/getpwuid_r.c new file mode 100644 index 0000000000..fae2a141be --- /dev/null +++ b/REORG.TODO/nscd/getpwuid_r.c @@ -0,0 +1,31 @@ +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <pwd.h> + + +#define LOOKUP_TYPE struct passwd +#define FUNCTION_NAME getpwuid +#define DATABASE_NAME passwd +#define ADD_PARAMS uid_t uid +#define ADD_VARIABLES uid +#define BUFLEN NSS_BUFLEN_PASSWD + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include <nss/getXXbyYY_r.c> diff --git a/REORG.TODO/nscd/getsrvbynm_r.c b/REORG.TODO/nscd/getsrvbynm_r.c new file mode 100644 index 0000000000..ef6eac0358 --- /dev/null +++ b/REORG.TODO/nscd/getsrvbynm_r.c @@ -0,0 +1,30 @@ +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <netdb.h> + + +#define LOOKUP_TYPE struct servent +#define FUNCTION_NAME getservbyname +#define DATABASE_NAME services +#define ADD_PARAMS const char *name, const char *proto +#define ADD_VARIABLES name, proto + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include "../nss/getXXbyYY_r.c" diff --git a/REORG.TODO/nscd/getsrvbypt_r.c b/REORG.TODO/nscd/getsrvbypt_r.c new file mode 100644 index 0000000000..10a3d57ab4 --- /dev/null +++ b/REORG.TODO/nscd/getsrvbypt_r.c @@ -0,0 +1,30 @@ +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <netdb.h> + + +#define LOOKUP_TYPE struct servent +#define FUNCTION_NAME getservbyport +#define DATABASE_NAME services +#define ADD_PARAMS int port, const char *proto +#define ADD_VARIABLES port, proto + +/* We are nscd, so we don't want to be talking to ourselves. */ +#undef USE_NSCD + +#include "../nss/getXXbyYY_r.c" diff --git a/REORG.TODO/nscd/grpcache.c b/REORG.TODO/nscd/grpcache.c new file mode 100644 index 0000000000..d2ad53509d --- /dev/null +++ b/REORG.TODO/nscd/grpcache.c @@ -0,0 +1,572 @@ +/* Cache handling for group lookup. + Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> +#include <assert.h> +#include <errno.h> +#include <error.h> +#include <grp.h> +#include <libintl.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <stackinfo.h> + +#include "nscd.h" +#include "dbg_log.h" +#ifdef HAVE_SENDFILE +# include <kernel-features.h> +#endif + +/* This is the standard reply in case the service is disabled. */ +static const gr_response_header disabled = +{ + .version = NSCD_VERSION, + .found = -1, + .gr_name_len = 0, + .gr_passwd_len = 0, + .gr_gid = -1, + .gr_mem_cnt = 0, +}; + +/* This is the struct describing how to write this record. */ +const struct iovec grp_iov_disabled = +{ + .iov_base = (void *) &disabled, + .iov_len = sizeof (disabled) +}; + + +/* This is the standard reply in case we haven't found the dataset. */ +static const gr_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .gr_name_len = 0, + .gr_passwd_len = 0, + .gr_gid = -1, + .gr_mem_cnt = 0, +}; + + +static time_t +cache_addgr (struct database_dyn *db, int fd, request_header *req, + const void *key, struct group *grp, uid_t owner, + struct hashentry *const he, struct datahead *dh, int errval) +{ + bool all_written = true; + ssize_t total; + time_t t = time (NULL); + + /* We allocate all data in one memory block: the iov vector, + the response header and the dataset itself. */ + struct dataset + { + struct datahead head; + gr_response_header resp; + char strdata[0]; + } *dataset; + + assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data)); + + time_t timeout = MAX_TIMEOUT_VALUE; + if (grp == NULL) + { + if (he != NULL && errval == EAGAIN) + { + /* If we have an old record available but cannot find one + now because the service is not available we keep the old + record and make sure it does not get removed. */ + if (reload_count != UINT_MAX) + /* Do not reset the value if we never not reload the record. */ + dh->nreloads = reload_count - 1; + + /* Reload with the same time-to-live value. */ + timeout = dh->timeout = t + db->postimeout; + + total = 0; + } + else + { + /* We have no data. This means we send the standard reply for this + case. */ + total = sizeof (notfound); + + if (fd != -1 + && TEMP_FAILURE_RETRY (send (fd, ¬found, total, + MSG_NOSIGNAL)) != total) + all_written = false; + + /* If we have a transient error or cannot permanently store + the result, so be it. */ + if (errno == EAGAIN || __builtin_expect (db->negtimeout == 0, 0)) + { + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + else if ((dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1)) != NULL) + { + timeout = datahead_init_neg (&dataset->head, + (sizeof (struct dataset) + + req->key_len), total, + db->negtimeout); + + /* This is the reply. */ + memcpy (&dataset->resp, ¬found, total); + + /* Copy the key data. */ + memcpy (dataset->strdata, key, req->key_len); + + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + + sizeof (struct dataset) + req->key_len, MS_ASYNC); + } + + (void) cache_add (req->type, &dataset->strdata, req->key_len, + &dataset->head, true, db, owner, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + } + } + else + { + /* Determine the I/O structure. */ + size_t gr_name_len = strlen (grp->gr_name) + 1; + size_t gr_passwd_len = strlen (grp->gr_passwd) + 1; + size_t gr_mem_cnt = 0; + uint32_t *gr_mem_len; + size_t gr_mem_len_total = 0; + char *gr_name; + char *cp; + const size_t key_len = strlen (key); + const size_t buf_len = 3 * sizeof (grp->gr_gid) + key_len + 1; + size_t alloca_used = 0; + char *buf = alloca_account (buf_len, alloca_used); + ssize_t n; + size_t cnt; + + /* We need this to insert the `bygid' entry. */ + int key_offset; + n = snprintf (buf, buf_len, "%d%c%n%s", grp->gr_gid, '\0', + &key_offset, (char *) key) + 1; + + /* Determine the length of all members. */ + while (grp->gr_mem[gr_mem_cnt]) + ++gr_mem_cnt; + gr_mem_len = alloca_account (gr_mem_cnt * sizeof (uint32_t), alloca_used); + for (gr_mem_cnt = 0; grp->gr_mem[gr_mem_cnt]; ++gr_mem_cnt) + { + gr_mem_len[gr_mem_cnt] = strlen (grp->gr_mem[gr_mem_cnt]) + 1; + gr_mem_len_total += gr_mem_len[gr_mem_cnt]; + } + + total = (offsetof (struct dataset, strdata) + + gr_mem_cnt * sizeof (uint32_t) + + gr_name_len + gr_passwd_len + gr_mem_len_total); + + /* If we refill the cache, first assume the reconrd did not + change. Allocate memory on the cache since it is likely + discarded anyway. If it turns out to be necessary to have a + new record we can still allocate real memory. */ + bool dataset_temporary = false; + bool dataset_malloced = false; + dataset = NULL; + + if (he == NULL) + { + /* Prevent an INVALIDATE request from pruning the data between + the two calls to cache_add. */ + if (db->propagate) + pthread_mutex_lock (&db->prune_run_lock); + dataset = (struct dataset *) mempool_alloc (db, total + n, 1); + } + + if (dataset == NULL) + { + if (he == NULL && db->propagate) + pthread_mutex_unlock (&db->prune_run_lock); + + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + if (! __libc_use_alloca (alloca_used + total + n)) + { + dataset = malloc (total + n); + /* Perhaps we should log a message that we were unable + to allocate memory for a large request. */ + if (dataset == NULL) + goto out; + dataset_malloced = true; + } + else + dataset = alloca_account (total + n, alloca_used); + + /* We cannot add this record to the permanent database. */ + dataset_temporary = true; + } + + timeout = datahead_init_pos (&dataset->head, total + n, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + db->postimeout); + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.gr_name_len = gr_name_len; + dataset->resp.gr_passwd_len = gr_passwd_len; + dataset->resp.gr_gid = grp->gr_gid; + dataset->resp.gr_mem_cnt = gr_mem_cnt; + + cp = dataset->strdata; + + /* This is the member string length array. */ + cp = mempcpy (cp, gr_mem_len, gr_mem_cnt * sizeof (uint32_t)); + gr_name = cp; + cp = mempcpy (cp, grp->gr_name, gr_name_len); + cp = mempcpy (cp, grp->gr_passwd, gr_passwd_len); + + for (cnt = 0; cnt < gr_mem_cnt; ++cnt) + cp = mempcpy (cp, grp->gr_mem[cnt], gr_mem_len[cnt]); + + /* Finally the stringified GID value. */ + memcpy (cp, buf, n); + char *key_copy = cp + key_offset; + assert (key_copy == (char *) rawmemchr (cp, '\0') + 1); + + assert (cp == dataset->strdata + total - offsetof (struct dataset, + strdata)); + + /* Now we can determine whether on refill we have to create a new + record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (total + n == dh->allocsize + && total - offsetof (struct dataset, resp) == dh->recsize + && memcmp (&dataset->resp, dh->data, + dh->allocsize - offsetof (struct dataset, resp)) == 0) + { + /* The data has not changed. We will just bump the + timeout value. Note that the new record has been + allocated on the stack and need not be freed. */ + dh->timeout = dataset->head.timeout; + ++dh->nreloads; + + /* If the new record was allocated via malloc, then we must free + it here. */ + if (dataset_malloced) + free (dataset); + } + else + { + /* We have to create a new record. Just allocate + appropriate memory and copy it. */ + struct dataset *newp + = (struct dataset *) mempool_alloc (db, total + n, 1); + if (newp != NULL) + { + /* Adjust pointers into the memory block. */ + gr_name = (char *) newp + (gr_name - (char *) dataset); + cp = (char *) newp + (cp - (char *) dataset); + key_copy = (char *) newp + (key_copy - (char *) dataset); + + dataset = memcpy (newp, dataset, total + n); + dataset_temporary = false; + } + + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + else + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so would + unnecessarily let the receiver wait. */ + assert (fd != -1); + +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && ! dataset_temporary) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + + total + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); + ssize_t written = sendfileall (fd, db->wr_fd, + (char *) &dataset->resp + - (char *) db->head, + dataset->head.recsize); + if (written != dataset->head.recsize) + { +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + all_written = false; + } + } + else +# ifndef __ASSUME_SENDFILE + use_write: +# endif +#endif + if (writeall (fd, &dataset->resp, dataset->head.recsize) + != dataset->head.recsize) + all_written = false; + } + + /* Add the record to the database. But only if it has not been + stored on the stack. */ + if (! dataset_temporary) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + total + n, + MS_ASYNC); + } + + /* NB: in the following code we always must add the entry + marked with FIRST first. Otherwise we end up with + dangling "pointers" in case a latter hash entry cannot be + added. */ + bool first = true; + + /* If the request was by GID, add that entry first. */ + if (req->type == GETGRBYGID) + { + if (cache_add (GETGRBYGID, cp, key_offset, &dataset->head, true, + db, owner, he == NULL) < 0) + goto out; + + first = false; + } + /* If the key is different from the name add a separate entry. */ + else if (strcmp (key_copy, gr_name) != 0) + { + if (cache_add (GETGRBYNAME, key_copy, key_len + 1, + &dataset->head, true, db, owner, he == NULL) < 0) + goto out; + + first = false; + } + + /* We have to add the value for both, byname and byuid. */ + if ((req->type == GETGRBYNAME || db->propagate) + && __builtin_expect (cache_add (GETGRBYNAME, gr_name, + gr_name_len, + &dataset->head, first, db, owner, + he == NULL) + == 0, 1)) + { + if (req->type == GETGRBYNAME && db->propagate) + (void) cache_add (GETGRBYGID, cp, key_offset, &dataset->head, + false, db, owner, false); + } + + out: + pthread_rwlock_unlock (&db->lock); + if (he == NULL && db->propagate) + pthread_mutex_unlock (&db->prune_run_lock); + } + } + + if (__builtin_expect (!all_written, 0) && debug_level > 0) + { + char buf[256]; + dbg_log (_("short write in %s: %s"), __FUNCTION__, + strerror_r (errno, buf, sizeof (buf))); + } + + return timeout; +} + + +union keytype +{ + void *v; + gid_t g; +}; + + +static int +lookup (int type, union keytype key, struct group *resultbufp, char *buffer, + size_t buflen, struct group **grp) +{ + if (type == GETGRBYNAME) + return __getgrnam_r (key.v, resultbufp, buffer, buflen, grp); + else + return __getgrgid_r (key.g, resultbufp, buffer, buflen, grp); +} + + +static time_t +addgrbyX (struct database_dyn *db, int fd, request_header *req, + union keytype key, const char *keystr, uid_t uid, + struct hashentry *he, struct datahead *dh) +{ + /* Search for the entry matching the key. Please note that we don't + look again in the table whether the dataset is now available. We + simply insert it. It does not matter if it is in there twice. The + pruning function only will look at the timestamp. */ + size_t buflen = 1024; + char *buffer = (char *) alloca (buflen); + struct group resultbuf; + struct group *grp; + bool use_malloc = false; + int errval = 0; + + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in group cache!"), keystr); + else + dbg_log (_("Reloading \"%s\" in group cache!"), keystr); + } + + while (lookup (req->type, key, &resultbuf, buffer, buflen, &grp) != 0 + && (errval = errno) == ERANGE) + { + errno = 0; + + if (__glibc_unlikely (buflen > 32768)) + { + char *old_buffer = buffer; + buflen *= 2; + buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen); + if (buffer == NULL) + { + /* We ran out of memory. We cannot do anything but + sending a negative response. In reality this should + never happen. */ + grp = NULL; + buffer = old_buffer; + + /* We set the error to indicate this is (possibly) a + temporary error and that it does not mean the entry + is not available at all. */ + errval = EAGAIN; + break; + } + use_malloc = true; + } + else + /* Allocate a new buffer on the stack. If possible combine it + with the previously allocated buffer. */ + buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen); + } + + time_t timeout = cache_addgr (db, fd, req, keystr, grp, uid, he, dh, errval); + + if (use_malloc) + free (buffer); + + return timeout; +} + + +void +addgrbyname (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + union keytype u = { .v = key }; + + addgrbyX (db, fd, req, u, key, uid, NULL, NULL); +} + + +time_t +readdgrbyname (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETGRBYNAME, + .key_len = he->len + }; + union keytype u = { .v = db->data + he->key }; + + return addgrbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh); +} + + +void +addgrbygid (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + char *ep; + gid_t gid = strtoul ((char *) key, &ep, 10); + + if (*(char *) key == '\0' || *ep != '\0') /* invalid numeric uid */ + { + if (debug_level > 0) + dbg_log (_("Invalid numeric gid \"%s\"!"), (char *) key); + + errno = EINVAL; + return; + } + + union keytype u = { .g = gid }; + + addgrbyX (db, fd, req, u, key, uid, NULL, NULL); +} + + +time_t +readdgrbygid (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + char *ep; + gid_t gid = strtoul (db->data + he->key, &ep, 10); + + /* Since the key has been added before it must be OK. */ + assert (*(db->data + he->key) != '\0' && *ep == '\0'); + + request_header req = + { + .type = GETGRBYGID, + .key_len = he->len + }; + union keytype u = { .g = gid }; + + return addgrbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh); +} diff --git a/REORG.TODO/nscd/hstcache.c b/REORG.TODO/nscd/hstcache.c new file mode 100644 index 0000000000..9f6ce979ac --- /dev/null +++ b/REORG.TODO/nscd/hstcache.c @@ -0,0 +1,619 @@ +/* Cache handling for host lookup. + Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> +#include <assert.h> +#include <errno.h> +#include <error.h> +#include <libintl.h> +#include <netdb.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <stdint.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <sys/mman.h> +#include <stackinfo.h> + +#include "nscd.h" +#include "dbg_log.h" +#ifdef HAVE_SENDFILE +# include <kernel-features.h> +#endif + + +/* This is the standard reply in case the service is disabled. */ +static const hst_response_header disabled = +{ + .version = NSCD_VERSION, + .found = -1, + .h_name_len = 0, + .h_aliases_cnt = 0, + .h_addrtype = -1, + .h_length = -1, + .h_addr_list_cnt = 0, + .error = NETDB_INTERNAL +}; + +/* This is the struct describing how to write this record. */ +const struct iovec hst_iov_disabled = +{ + .iov_base = (void *) &disabled, + .iov_len = sizeof (disabled) +}; + + +/* This is the standard reply in case we haven't found the dataset. */ +static const hst_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .h_name_len = 0, + .h_aliases_cnt = 0, + .h_addrtype = -1, + .h_length = -1, + .h_addr_list_cnt = 0, + .error = HOST_NOT_FOUND +}; + + +/* This is the standard reply in case there are temporary problems. */ +static const hst_response_header tryagain = +{ + .version = NSCD_VERSION, + .found = 0, + .h_name_len = 0, + .h_aliases_cnt = 0, + .h_addrtype = -1, + .h_length = -1, + .h_addr_list_cnt = 0, + .error = TRY_AGAIN +}; + + +static time_t +cache_addhst (struct database_dyn *db, int fd, request_header *req, + const void *key, struct hostent *hst, uid_t owner, + struct hashentry *const he, struct datahead *dh, int errval, + int32_t ttl) +{ + bool all_written = true; + time_t t = time (NULL); + + /* We allocate all data in one memory block: the iov vector, + the response header and the dataset itself. */ + struct dataset + { + struct datahead head; + hst_response_header resp; + char strdata[0]; + } *dataset; + + assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data)); + + time_t timeout = MAX_TIMEOUT_VALUE; + if (hst == NULL) + { + if (he != NULL && errval == EAGAIN) + { + /* If we have an old record available but cannot find one + now because the service is not available we keep the old + record and make sure it does not get removed. */ + if (reload_count != UINT_MAX) + /* Do not reset the value if we never not reload the record. */ + dh->nreloads = reload_count - 1; + + /* Reload with the same time-to-live value. */ + timeout = dh->timeout = t + dh->ttl; + } + else + { + /* We have no data. This means we send the standard reply for this + case. Possibly this is only temporary. */ + ssize_t total = sizeof (notfound); + assert (sizeof (notfound) == sizeof (tryagain)); + + const hst_response_header *resp = (errval == EAGAIN + ? &tryagain : ¬found); + + if (fd != -1 && + TEMP_FAILURE_RETRY (send (fd, resp, total, + MSG_NOSIGNAL)) != total) + all_written = false; + + /* If we have a transient error or cannot permanently store + the result, so be it. */ + if (errval == EAGAIN || __builtin_expect (db->negtimeout == 0, 0)) + { + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + else if ((dataset = mempool_alloc (db, (sizeof (struct dataset) + + req->key_len), 1)) != NULL) + { + timeout = datahead_init_neg (&dataset->head, + (sizeof (struct dataset) + + req->key_len), total, + (ttl == INT32_MAX + ? db->negtimeout : ttl)); + + /* This is the reply. */ + memcpy (&dataset->resp, resp, total); + + /* Copy the key data. */ + memcpy (dataset->strdata, key, req->key_len); + + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + + sizeof (struct dataset) + req->key_len, MS_ASYNC); + } + + (void) cache_add (req->type, &dataset->strdata, req->key_len, + &dataset->head, true, db, owner, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + } + } + else + { + /* Determine the I/O structure. */ + size_t h_name_len = strlen (hst->h_name) + 1; + size_t h_aliases_cnt; + uint32_t *h_aliases_len; + size_t h_addr_list_cnt; + char *addresses; + char *aliases; + char *key_copy = NULL; + char *cp; + size_t cnt; + ssize_t total; + + /* Determine the number of aliases. */ + h_aliases_cnt = 0; + for (cnt = 0; hst->h_aliases[cnt] != NULL; ++cnt) + ++h_aliases_cnt; + /* Determine the length of all aliases. */ + h_aliases_len = (uint32_t *) alloca (h_aliases_cnt * sizeof (uint32_t)); + total = 0; + for (cnt = 0; cnt < h_aliases_cnt; ++cnt) + { + h_aliases_len[cnt] = strlen (hst->h_aliases[cnt]) + 1; + total += h_aliases_len[cnt]; + } + + /* Determine the number of addresses. */ + h_addr_list_cnt = 0; + while (hst->h_addr_list[h_addr_list_cnt] != NULL) + ++h_addr_list_cnt; + + if (h_addr_list_cnt == 0) + /* Invalid entry. */ + return MAX_TIMEOUT_VALUE; + + total += (sizeof (struct dataset) + + h_name_len + + h_aliases_cnt * sizeof (uint32_t) + + h_addr_list_cnt * hst->h_length); + + /* If we refill the cache, first assume the reconrd did not + change. Allocate memory on the cache since it is likely + discarded anyway. If it turns out to be necessary to have a + new record we can still allocate real memory. */ + bool alloca_used = false; + dataset = NULL; + + /* If the record contains more than one IP address (used for + load balancing etc) don't cache the entry. This is something + the current cache handling cannot handle and it is more than + questionable whether it is worthwhile complicating the cache + handling just for handling such a special case. */ + if (he == NULL && h_addr_list_cnt == 1) + dataset = (struct dataset *) mempool_alloc (db, total + req->key_len, + 1); + + if (dataset == NULL) + { + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + dataset = (struct dataset *) alloca (total + req->key_len); + + /* We cannot add this record to the permanent database. */ + alloca_used = true; + } + + timeout = datahead_init_pos (&dataset->head, total + req->key_len, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + ttl == INT32_MAX ? db->postimeout : ttl); + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.h_name_len = h_name_len; + dataset->resp.h_aliases_cnt = h_aliases_cnt; + dataset->resp.h_addrtype = hst->h_addrtype; + dataset->resp.h_length = hst->h_length; + dataset->resp.h_addr_list_cnt = h_addr_list_cnt; + dataset->resp.error = NETDB_SUCCESS; + + /* Make sure there is no gap. */ + assert ((char *) (&dataset->resp.error + 1) == dataset->strdata); + + cp = dataset->strdata; + + cp = mempcpy (cp, hst->h_name, h_name_len); + cp = mempcpy (cp, h_aliases_len, h_aliases_cnt * sizeof (uint32_t)); + + /* The normal addresses first. */ + addresses = cp; + for (cnt = 0; cnt < h_addr_list_cnt; ++cnt) + cp = mempcpy (cp, hst->h_addr_list[cnt], hst->h_length); + + /* Then the aliases. */ + aliases = cp; + for (cnt = 0; cnt < h_aliases_cnt; ++cnt) + cp = mempcpy (cp, hst->h_aliases[cnt], h_aliases_len[cnt]); + + assert (cp + == dataset->strdata + total - offsetof (struct dataset, + strdata)); + + /* If we are adding a GETHOSTBYNAME{,v6} entry we must be prepared + that the answer we get from the NSS does not contain the key + itself. This is the case if the resolver is used and the name + is extended by the domainnames from /etc/resolv.conf. Therefore + we explicitly add the name here. */ + key_copy = memcpy (cp, key, req->key_len); + + assert ((char *) &dataset->resp + dataset->head.recsize == cp); + + /* Now we can determine whether on refill we have to create a new + record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (total + req->key_len == dh->allocsize + && total - offsetof (struct dataset, resp) == dh->recsize + && memcmp (&dataset->resp, dh->data, + dh->allocsize - offsetof (struct dataset, resp)) == 0) + { + /* The data has not changed. We will just bump the + timeout value. Note that the new record has been + allocated on the stack and need not be freed. */ + assert (h_addr_list_cnt == 1); + dh->ttl = dataset->head.ttl; + dh->timeout = dataset->head.timeout; + ++dh->nreloads; + } + else + { + if (h_addr_list_cnt == 1) + { + /* We have to create a new record. Just allocate + appropriate memory and copy it. */ + struct dataset *newp + = (struct dataset *) mempool_alloc (db, + total + req->key_len, + 1); + if (newp != NULL) + { + /* Adjust pointers into the memory block. */ + addresses = (char *) newp + (addresses + - (char *) dataset); + aliases = (char *) newp + (aliases - (char *) dataset); + assert (key_copy != NULL); + key_copy = (char *) newp + (key_copy - (char *) dataset); + + dataset = memcpy (newp, dataset, total + req->key_len); + alloca_used = false; + } + } + + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + else + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so would + unnecessarily keep the receiver waiting. */ + assert (fd != -1); + +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && !alloca_used) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + + total + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); + ssize_t written = sendfileall (fd, db->wr_fd, + (char *) &dataset->resp + - (char *) db->head, + dataset->head.recsize); + if (written != dataset->head.recsize) + { +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + all_written = false; + } + } + else +# ifndef __ASSUME_SENDFILE + use_write: +# endif +#endif + if (writeall (fd, &dataset->resp, dataset->head.recsize) + != dataset->head.recsize) + all_written = false; + } + + /* Add the record to the database. But only if it has not been + stored on the stack. + + If the record contains more than one IP address (used for + load balancing etc) don't cache the entry. This is something + the current cache handling cannot handle and it is more than + questionable whether it is worthwhile complicating the cache + handling just for handling such a special case. */ + if (! alloca_used) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + + total + req->key_len, MS_ASYNC); + } + + /* NB: the following code is really complicated. It has + seemlingly duplicated code paths which do the same. The + problem is that we always must add the hash table entry + with the FIRST flag set first. Otherwise we get dangling + pointers in case memory allocation fails. */ + assert (hst->h_addr_list[1] == NULL); + + /* Avoid adding names if more than one address is available. See + above for more info. */ + assert (req->type == GETHOSTBYNAME + || req->type == GETHOSTBYNAMEv6 + || req->type == GETHOSTBYADDR + || req->type == GETHOSTBYADDRv6); + + (void) cache_add (req->type, key_copy, req->key_len, + &dataset->head, true, db, owner, he == NULL); + + pthread_rwlock_unlock (&db->lock); + } + } + + if (__builtin_expect (!all_written, 0) && debug_level > 0) + { + char buf[256]; + dbg_log (_("short write in %s: %s"), __FUNCTION__, + strerror_r (errno, buf, sizeof (buf))); + } + + return timeout; +} + + +static int +lookup (int type, void *key, struct hostent *resultbufp, char *buffer, + size_t buflen, struct hostent **hst, int32_t *ttlp) +{ + if (type == GETHOSTBYNAME) + return __gethostbyname3_r (key, AF_INET, resultbufp, buffer, buflen, hst, + &h_errno, ttlp, NULL); + if (type == GETHOSTBYNAMEv6) + return __gethostbyname3_r (key, AF_INET6, resultbufp, buffer, buflen, hst, + &h_errno, ttlp, NULL); + if (type == GETHOSTBYADDR) + return __gethostbyaddr2_r (key, NS_INADDRSZ, AF_INET, resultbufp, buffer, + buflen, hst, &h_errno, ttlp); + return __gethostbyaddr2_r (key, NS_IN6ADDRSZ, AF_INET6, resultbufp, buffer, + buflen, hst, &h_errno, ttlp); +} + + +static time_t +addhstbyX (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid, struct hashentry *he, struct datahead *dh) +{ + /* Search for the entry matching the key. Please note that we don't + look again in the table whether the dataset is now available. We + simply insert it. It does not matter if it is in there twice. The + pruning function only will look at the timestamp. */ + int buflen = 1024; + char *buffer = (char *) alloca (buflen); + struct hostent resultbuf; + struct hostent *hst; + bool use_malloc = false; + int errval = 0; + int32_t ttl = INT32_MAX; + + if (__glibc_unlikely (debug_level > 0)) + { + const char *str; + char buf[INET6_ADDRSTRLEN + 1]; + if (req->type == GETHOSTBYNAME || req->type == GETHOSTBYNAMEv6) + str = key; + else + str = inet_ntop (req->type == GETHOSTBYADDR ? AF_INET : AF_INET6, + key, buf, sizeof (buf)); + + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in hosts cache!"), (char *) str); + else + dbg_log (_("Reloading \"%s\" in hosts cache!"), (char *) str); + } + + while (lookup (req->type, key, &resultbuf, buffer, buflen, &hst, &ttl) != 0 + && h_errno == NETDB_INTERNAL + && (errval = errno) == ERANGE) + { + errno = 0; + + if (__glibc_unlikely (buflen > 32768)) + { + char *old_buffer = buffer; + buflen *= 2; + buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen); + if (buffer == NULL) + { + /* We ran out of memory. We cannot do anything but + sending a negative response. In reality this should + never happen. */ + hst = NULL; + buffer = old_buffer; + + /* We set the error to indicate this is (possibly) a + temporary error and that it does not mean the entry + is not available at all. */ + h_errno = TRY_AGAIN; + errval = EAGAIN; + break; + } + use_malloc = true; + } + else + /* Allocate a new buffer on the stack. If possible combine it + with the previously allocated buffer. */ + buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen); + } + + time_t timeout = cache_addhst (db, fd, req, key, hst, uid, he, dh, + h_errno == TRY_AGAIN ? errval : 0, ttl); + + if (use_malloc) + free (buffer); + + return timeout; +} + + +void +addhstbyname (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + addhstbyX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdhstbyname (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETHOSTBYNAME, + .key_len = he->len + }; + + return addhstbyX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} + + +void +addhstbyaddr (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + addhstbyX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdhstbyaddr (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETHOSTBYADDR, + .key_len = he->len + }; + + return addhstbyX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} + + +void +addhstbynamev6 (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + addhstbyX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdhstbynamev6 (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETHOSTBYNAMEv6, + .key_len = he->len + }; + + return addhstbyX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} + + +void +addhstbyaddrv6 (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + addhstbyX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdhstbyaddrv6 (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETHOSTBYADDRv6, + .key_len = he->len + }; + + return addhstbyX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} diff --git a/REORG.TODO/nscd/initgrcache.c b/REORG.TODO/nscd/initgrcache.c new file mode 100644 index 0000000000..4deb483fbb --- /dev/null +++ b/REORG.TODO/nscd/initgrcache.c @@ -0,0 +1,438 @@ +/* Cache handling for host lookup. + Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2004. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <grp.h> +#include <libintl.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/mman.h> +#include <scratch_buffer.h> + +#include "dbg_log.h" +#include "nscd.h" +#ifdef HAVE_SENDFILE +# include <kernel-features.h> +#endif + +#include "../nss/nsswitch.h" + + +/* Type of the lookup function. */ +typedef enum nss_status (*initgroups_dyn_function) (const char *, gid_t, + long int *, long int *, + gid_t **, long int, int *); + + +static const initgr_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .ngrps = 0 +}; + + +#include "../grp/compat-initgroups.c" + + +static time_t +addinitgroupsX (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid, struct hashentry *const he, + struct datahead *dh) +{ + /* Search for the entry matching the key. Please note that we don't + look again in the table whether the dataset is now available. We + simply insert it. It does not matter if it is in there twice. The + pruning function only will look at the timestamp. */ + + + /* We allocate all data in one memory block: the iov vector, + the response header and the dataset itself. */ + struct dataset + { + struct datahead head; + initgr_response_header resp; + char strdata[0]; + } *dataset = NULL; + + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in group cache!"), (char *) key); + else + dbg_log (_("Reloading \"%s\" in group cache!"), (char *) key); + } + + static service_user *group_database; + service_user *nip; + int no_more; + + if (group_database == NULL) + no_more = __nss_database_lookup ("group", NULL, + "compat [NOTFOUND=return] files", + &group_database); + else + no_more = 0; + nip = group_database; + + /* We always use sysconf even if NGROUPS_MAX is defined. That way, the + limit can be raised in the kernel configuration without having to + recompile libc. */ + long int limit = __sysconf (_SC_NGROUPS_MAX); + + long int size; + if (limit > 0) + /* We limit the size of the intially allocated array. */ + size = MIN (limit, 64); + else + /* No fixed limit on groups. Pick a starting buffer size. */ + size = 16; + + long int start = 0; + bool all_tryagain = true; + bool any_success = false; + + /* This is temporary memory, we need not (and must not) call + mempool_alloc. */ + // XXX This really should use alloca. need to change the backends. + gid_t *groups = (gid_t *) malloc (size * sizeof (gid_t)); + if (__glibc_unlikely (groups == NULL)) + /* No more memory. */ + goto out; + + /* Nothing added yet. */ + while (! no_more) + { + long int prev_start = start; + enum nss_status status; + initgroups_dyn_function fct; + fct = __nss_lookup_function (nip, "initgroups_dyn"); + + if (fct == NULL) + { + status = compat_call (nip, key, -1, &start, &size, &groups, + limit, &errno); + + if (nss_next_action (nip, NSS_STATUS_UNAVAIL) != NSS_ACTION_CONTINUE) + break; + } + else + status = DL_CALL_FCT (fct, (key, -1, &start, &size, &groups, + limit, &errno)); + + /* Remove duplicates. */ + long int cnt = prev_start; + while (cnt < start) + { + long int inner; + for (inner = 0; inner < prev_start; ++inner) + if (groups[inner] == groups[cnt]) + break; + + if (inner < prev_start) + groups[cnt] = groups[--start]; + else + ++cnt; + } + + if (status != NSS_STATUS_TRYAGAIN) + all_tryagain = false; + + /* This is really only for debugging. */ + if (NSS_STATUS_TRYAGAIN > status || status > NSS_STATUS_RETURN) + __libc_fatal ("illegal status in internal_getgrouplist"); + + any_success |= status == NSS_STATUS_SUCCESS; + + if (status != NSS_STATUS_SUCCESS + && nss_next_action (nip, status) == NSS_ACTION_RETURN) + break; + + if (nip->next == NULL) + no_more = -1; + else + nip = nip->next; + } + + bool all_written; + ssize_t total; + time_t timeout; + out: + all_written = true; + timeout = MAX_TIMEOUT_VALUE; + if (!any_success) + { + /* Nothing found. Create a negative result record. */ + total = sizeof (notfound); + + if (he != NULL && all_tryagain) + { + /* If we have an old record available but cannot find one now + because the service is not available we keep the old record + and make sure it does not get removed. */ + if (reload_count != UINT_MAX && dh->nreloads == reload_count) + /* Do not reset the value if we never not reload the record. */ + dh->nreloads = reload_count - 1; + + /* Reload with the same time-to-live value. */ + timeout = dh->timeout = time (NULL) + db->postimeout; + } + else + { + /* We have no data. This means we send the standard reply for this + case. */ + if (fd != -1 + && TEMP_FAILURE_RETRY (send (fd, ¬found, total, + MSG_NOSIGNAL)) != total) + all_written = false; + + /* If we have a transient error or cannot permanently store + the result, so be it. */ + if (all_tryagain || __builtin_expect (db->negtimeout == 0, 0)) + { + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + else if ((dataset = mempool_alloc (db, (sizeof (struct dataset) + + req->key_len), 1)) != NULL) + { + timeout = datahead_init_neg (&dataset->head, + (sizeof (struct dataset) + + req->key_len), total, + db->negtimeout); + + /* This is the reply. */ + memcpy (&dataset->resp, ¬found, total); + + /* Copy the key data. */ + char *key_copy = memcpy (dataset->strdata, key, req->key_len); + + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + + sizeof (struct dataset) + req->key_len, MS_ASYNC); + } + + (void) cache_add (req->type, key_copy, req->key_len, + &dataset->head, true, db, uid, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + } + } + else + { + + total = offsetof (struct dataset, strdata) + start * sizeof (int32_t); + + /* If we refill the cache, first assume the reconrd did not + change. Allocate memory on the cache since it is likely + discarded anyway. If it turns out to be necessary to have a + new record we can still allocate real memory. */ + bool alloca_used = false; + dataset = NULL; + + if (he == NULL) + dataset = (struct dataset *) mempool_alloc (db, total + req->key_len, + 1); + + if (dataset == NULL) + { + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + dataset = (struct dataset *) alloca (total + req->key_len); + + /* We cannot add this record to the permanent database. */ + alloca_used = true; + } + + timeout = datahead_init_pos (&dataset->head, total + req->key_len, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + db->postimeout); + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.ngrps = start; + + char *cp = dataset->strdata; + + /* Copy the GID values. If the size of the types match this is + very simple. */ + if (sizeof (gid_t) == sizeof (int32_t)) + cp = mempcpy (cp, groups, start * sizeof (gid_t)); + else + { + gid_t *gcp = (gid_t *) cp; + + for (int i = 0; i < start; ++i) + *gcp++ = groups[i]; + + cp = (char *) gcp; + } + + /* Finally the user name. */ + memcpy (cp, key, req->key_len); + + assert (cp == dataset->strdata + total - offsetof (struct dataset, + strdata)); + + /* Now we can determine whether on refill we have to create a new + record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (total + req->key_len == dh->allocsize + && total - offsetof (struct dataset, resp) == dh->recsize + && memcmp (&dataset->resp, dh->data, + dh->allocsize - offsetof (struct dataset, resp)) == 0) + { + /* The data has not changed. We will just bump the + timeout value. Note that the new record has been + allocated on the stack and need not be freed. */ + dh->timeout = dataset->head.timeout; + ++dh->nreloads; + } + else + { + /* We have to create a new record. Just allocate + appropriate memory and copy it. */ + struct dataset *newp + = (struct dataset *) mempool_alloc (db, total + req->key_len, + 1); + if (newp != NULL) + { + /* Adjust pointer into the memory block. */ + cp = (char *) newp + (cp - (char *) dataset); + + dataset = memcpy (newp, dataset, total + req->key_len); + alloca_used = false; + } + + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + else + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so would + unnecessarily let the receiver wait. */ + assert (fd != -1); + +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && !alloca_used) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + + total + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); + ssize_t written = sendfileall (fd, db->wr_fd, + (char *) &dataset->resp + - (char *) db->head, + dataset->head.recsize); + if (written != dataset->head.recsize) + { +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + all_written = false; + } + } + else +# ifndef __ASSUME_SENDFILE + use_write: +# endif +#endif + if (writeall (fd, &dataset->resp, dataset->head.recsize) + != dataset->head.recsize) + all_written = false; + } + + + /* Add the record to the database. But only if it has not been + stored on the stack. */ + if (! alloca_used) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + total + + req->key_len, MS_ASYNC); + } + + (void) cache_add (INITGROUPS, cp, req->key_len, &dataset->head, true, + db, uid, he == NULL); + + pthread_rwlock_unlock (&db->lock); + } + } + + free (groups); + + if (__builtin_expect (!all_written, 0) && debug_level > 0) + { + char buf[256]; + dbg_log (_("short write in %s: %s"), __FUNCTION__, + strerror_r (errno, buf, sizeof (buf))); + } + + return timeout; +} + + +void +addinitgroups (struct database_dyn *db, int fd, request_header *req, void *key, + uid_t uid) +{ + addinitgroupsX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdinitgroups (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = INITGROUPS, + .key_len = he->len + }; + + return addinitgroupsX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} diff --git a/REORG.TODO/nscd/mem.c b/REORG.TODO/nscd/mem.c new file mode 100644 index 0000000000..092f3ae7c1 --- /dev/null +++ b/REORG.TODO/nscd/mem.c @@ -0,0 +1,589 @@ +/* Cache memory handling. + Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2004. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <inttypes.h> +#include <libintl.h> +#include <limits.h> +#include <obstack.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/param.h> + +#include "dbg_log.h" +#include "nscd.h" + + +static int +sort_he (const void *p1, const void *p2) +{ + struct hashentry *h1 = *(struct hashentry **) p1; + struct hashentry *h2 = *(struct hashentry **) p2; + + if (h1 < h2) + return -1; + if (h1 > h2) + return 1; + return 0; +} + + +static int +sort_he_data (const void *p1, const void *p2) +{ + struct hashentry *h1 = *(struct hashentry **) p1; + struct hashentry *h2 = *(struct hashentry **) p2; + + if (h1->packet < h2->packet) + return -1; + if (h1->packet > h2->packet) + return 1; + return 0; +} + + +/* Basic definitions for the bitmap implementation. Only BITMAP_T + needs to be changed to choose a different word size. */ +#define BITMAP_T uint8_t +#define BITS (CHAR_BIT * sizeof (BITMAP_T)) +#define ALLBITS ((((BITMAP_T) 1) << BITS) - 1) +#define HIGHBIT (((BITMAP_T) 1) << (BITS - 1)) + + +static void +markrange (BITMAP_T *mark, ref_t start, size_t len) +{ + /* Adjust parameters for block alignment. */ + assert ((start & BLOCK_ALIGN_M1) == 0); + start /= BLOCK_ALIGN; + len = (len + BLOCK_ALIGN_M1) / BLOCK_ALIGN; + + size_t elem = start / BITS; + + if (start % BITS != 0) + { + if (start % BITS + len <= BITS) + { + /* All fits in the partial byte. */ + mark[elem] |= (ALLBITS >> (BITS - len)) << (start % BITS); + return; + } + + mark[elem++] |= ALLBITS << (start % BITS); + len -= BITS - (start % BITS); + } + + while (len >= BITS) + { + mark[elem++] = ALLBITS; + len -= BITS; + } + + if (len > 0) + mark[elem] |= ALLBITS >> (BITS - len); +} + + +void +gc (struct database_dyn *db) +{ + /* We need write access. */ + pthread_rwlock_wrlock (&db->lock); + + /* And the memory handling lock. */ + pthread_mutex_lock (&db->memlock); + + /* We need an array representing the data area. All memory + allocation is BLOCK_ALIGN aligned so this is the level at which + we have to look at the memory. We use a mark and sweep algorithm + where the marks are placed in this array. */ + assert (db->head->first_free % BLOCK_ALIGN == 0); + + BITMAP_T *mark; + bool mark_use_malloc; + /* In prune_cache we are also using a dynamically allocated array. + If the array in the caller is too large we have malloc'ed it. */ + size_t stack_used = sizeof (bool) * db->head->module; + if (__glibc_unlikely (stack_used > MAX_STACK_USE)) + stack_used = 0; + size_t nmark = (db->head->first_free / BLOCK_ALIGN + BITS - 1) / BITS; + size_t memory_needed = nmark * sizeof (BITMAP_T); + if (__glibc_likely (stack_used + memory_needed <= MAX_STACK_USE)) + { + mark = (BITMAP_T *) alloca_account (memory_needed, stack_used); + mark_use_malloc = false; + memset (mark, '\0', memory_needed); + } + else + { + mark = (BITMAP_T *) xcalloc (1, memory_needed); + mark_use_malloc = true; + } + + /* Create an array which can hold pointer to all the entries in hash + entries. */ + memory_needed = 2 * db->head->nentries * sizeof (struct hashentry *); + struct hashentry **he; + struct hashentry **he_data; + bool he_use_malloc; + if (__glibc_likely (stack_used + memory_needed <= MAX_STACK_USE)) + { + he = alloca_account (memory_needed, stack_used); + he_use_malloc = false; + } + else + { + he = xmalloc (memory_needed); + he_use_malloc = true; + } + he_data = &he[db->head->nentries]; + + size_t cnt = 0; + for (size_t idx = 0; idx < db->head->module; ++idx) + { + ref_t *prevp = &db->head->array[idx]; + ref_t run = *prevp; + + while (run != ENDREF) + { + assert (cnt < db->head->nentries); + he[cnt] = (struct hashentry *) (db->data + run); + + he[cnt]->prevp = prevp; + prevp = &he[cnt]->next; + + /* This is the hash entry itself. */ + markrange (mark, run, sizeof (struct hashentry)); + + /* Add the information for the data itself. We do this + only for the one special entry marked with FIRST. */ + if (he[cnt]->first) + { + struct datahead *dh + = (struct datahead *) (db->data + he[cnt]->packet); + markrange (mark, he[cnt]->packet, dh->allocsize); + } + + run = he[cnt]->next; + + ++cnt; + } + } + assert (cnt == db->head->nentries); + + /* Sort the entries by the addresses of the referenced data. All + the entries pointing to the same DATAHEAD object will have the + same key. Stability of the sorting is unimportant. */ + memcpy (he_data, he, cnt * sizeof (struct hashentry *)); + qsort (he_data, cnt, sizeof (struct hashentry *), sort_he_data); + + /* Sort the entries by their address. */ + qsort (he, cnt, sizeof (struct hashentry *), sort_he); + +#define obstack_chunk_alloc xmalloc +#define obstack_chunk_free free + struct obstack ob; + obstack_init (&ob); + + /* Determine the highest used address. */ + size_t high = nmark; + while (high > 0 && mark[high - 1] == 0) + --high; + + /* No memory used. */ + if (high == 0) + { + db->head->first_free = 0; + goto out; + } + + /* Determine the highest offset. */ + BITMAP_T mask = HIGHBIT; + ref_t highref = (high * BITS - 1) * BLOCK_ALIGN; + while ((mark[high - 1] & mask) == 0) + { + mask >>= 1; + highref -= BLOCK_ALIGN; + } + + /* Now we can iterate over the MARK array and find bits which are not + set. These represent memory which can be recovered. */ + size_t byte = 0; + /* Find the first gap. */ + while (byte < high && mark[byte] == ALLBITS) + ++byte; + + if (byte == high + || (byte == high - 1 && (mark[byte] & ~(mask | (mask - 1))) == 0)) + /* No gap. */ + goto out; + + mask = 1; + cnt = 0; + while ((mark[byte] & mask) != 0) + { + ++cnt; + mask <<= 1; + } + ref_t off_free = (byte * BITS + cnt) * BLOCK_ALIGN; + assert (off_free <= db->head->first_free); + + struct hashentry **next_hash = he; + struct hashentry **next_data = he_data; + + /* Skip over the hash entries in the first block which does not get + moved. */ + while (next_hash < &he[db->head->nentries] + && *next_hash < (struct hashentry *) (db->data + off_free)) + ++next_hash; + + while (next_data < &he_data[db->head->nentries] + && (*next_data)->packet < off_free) + ++next_data; + + + /* Now we start modifying the data. Make sure all readers of the + data are aware of this and temporarily don't use the data. */ + ++db->head->gc_cycle; + assert ((db->head->gc_cycle & 1) == 1); + + + /* We do not perform the move operations right away since the + he_data array is not sorted by the address of the data. */ + struct moveinfo + { + void *from; + void *to; + size_t size; + struct moveinfo *next; + } *moves = NULL; + + while (byte < high) + { + /* Search for the next filled block. BYTE is the index of the + entry in MARK, MASK is the bit, and CNT is the bit number. + OFF_FILLED is the corresponding offset. */ + if ((mark[byte] & ~(mask - 1)) == 0) + { + /* No other bit set in the same element of MARK. Search in the + following memory. */ + do + ++byte; + while (byte < high && mark[byte] == 0); + + if (byte == high) + /* That was it. */ + break; + + mask = 1; + cnt = 0; + } + /* Find the exact bit. */ + while ((mark[byte] & mask) == 0) + { + ++cnt; + mask <<= 1; + } + + ref_t off_alloc = (byte * BITS + cnt) * BLOCK_ALIGN; + assert (off_alloc <= db->head->first_free); + + /* Find the end of the used area. */ + if ((mark[byte] & ~(mask - 1)) == (BITMAP_T) ~(mask - 1)) + { + /* All other bits set. Search the next bytes in MARK. */ + do + ++byte; + while (byte < high && mark[byte] == ALLBITS); + + mask = 1; + cnt = 0; + } + if (byte < high) + { + /* Find the exact bit. */ + while ((mark[byte] & mask) != 0) + { + ++cnt; + mask <<= 1; + } + } + + ref_t off_allocend = (byte * BITS + cnt) * BLOCK_ALIGN; + assert (off_allocend <= db->head->first_free); + /* Now we know that we can copy the area from OFF_ALLOC to + OFF_ALLOCEND (not included) to the memory starting at + OFF_FREE. First fix up all the entries for the + displacement. */ + ref_t disp = off_alloc - off_free; + + struct moveinfo *new_move; + if (__builtin_expect (stack_used + sizeof (*new_move) <= MAX_STACK_USE, + 1)) + new_move = alloca_account (sizeof (*new_move), stack_used); + else + new_move = obstack_alloc (&ob, sizeof (*new_move)); + new_move->from = db->data + off_alloc; + new_move->to = db->data + off_free; + new_move->size = off_allocend - off_alloc; + /* Create a circular list to be always able to append at the end. */ + if (moves == NULL) + moves = new_move->next = new_move; + else + { + new_move->next = moves->next; + moves = moves->next = new_move; + } + + /* The following loop will prepare to move this much data. */ + off_free += off_allocend - off_alloc; + + while (off_alloc < off_allocend) + { + /* Determine whether the next entry is for a hash entry or + the data. */ + if ((struct hashentry *) (db->data + off_alloc) == *next_hash) + { + /* Just correct the forward reference. */ + *(*next_hash++)->prevp -= disp; + + off_alloc += ((sizeof (struct hashentry) + BLOCK_ALIGN_M1) + & ~BLOCK_ALIGN_M1); + } + else + { + assert (next_data < &he_data[db->head->nentries]); + assert ((*next_data)->packet == off_alloc); + + struct datahead *dh = (struct datahead *) (db->data + off_alloc); + do + { + assert ((*next_data)->key >= (*next_data)->packet); + assert ((*next_data)->key + (*next_data)->len + <= (*next_data)->packet + dh->allocsize); + + (*next_data)->packet -= disp; + (*next_data)->key -= disp; + ++next_data; + } + while (next_data < &he_data[db->head->nentries] + && (*next_data)->packet == off_alloc); + + off_alloc += (dh->allocsize + BLOCK_ALIGN_M1) & ~BLOCK_ALIGN_M1; + } + } + assert (off_alloc == off_allocend); + + assert (off_alloc <= db->head->first_free); + if (off_alloc == db->head->first_free) + /* We are done, that was the last block. */ + break; + } + assert (next_hash == &he[db->head->nentries]); + assert (next_data == &he_data[db->head->nentries]); + + /* Now perform the actual moves. */ + if (moves != NULL) + { + struct moveinfo *runp = moves->next; + do + { + assert ((char *) runp->to >= db->data); + assert ((char *) runp->to + runp->size + <= db->data + db->head->first_free); + assert ((char *) runp->from >= db->data); + assert ((char *) runp->from + runp->size + <= db->data + db->head->first_free); + + /* The regions may overlap. */ + memmove (runp->to, runp->from, runp->size); + runp = runp->next; + } + while (runp != moves->next); + + if (__glibc_unlikely (debug_level >= 3)) + dbg_log (_("freed %zu bytes in %s cache"), + (size_t) (db->head->first_free + - ((char *) moves->to + moves->size - db->data)), + dbnames[db - dbs]); + + /* The byte past the end of the last copied block is the next + available byte. */ + db->head->first_free = (char *) moves->to + moves->size - db->data; + + /* Consistency check. */ + if (__glibc_unlikely (debug_level >= 3)) + { + for (size_t idx = 0; idx < db->head->module; ++idx) + { + ref_t run = db->head->array[idx]; + size_t cnt = 0; + + while (run != ENDREF) + { + if (run + sizeof (struct hashentry) > db->head->first_free) + { + dbg_log ("entry %zu in hash bucket %zu out of bounds: " + "%" PRIu32 "+%zu > %zu\n", + cnt, idx, run, sizeof (struct hashentry), + (size_t) db->head->first_free); + break; + } + + struct hashentry *he = (struct hashentry *) (db->data + run); + + if (he->key + he->len > db->head->first_free) + dbg_log ("key of entry %zu in hash bucket %zu out of " + "bounds: %" PRIu32 "+%zu > %zu\n", + cnt, idx, he->key, (size_t) he->len, + (size_t) db->head->first_free); + + if (he->packet + sizeof (struct datahead) + > db->head->first_free) + dbg_log ("packet of entry %zu in hash bucket %zu out of " + "bounds: %" PRIu32 "+%zu > %zu\n", + cnt, idx, he->packet, sizeof (struct datahead), + (size_t) db->head->first_free); + else + { + struct datahead *dh = (struct datahead *) (db->data + + he->packet); + if (he->packet + dh->allocsize + > db->head->first_free) + dbg_log ("full key of entry %zu in hash bucket %zu " + "out of bounds: %" PRIu32 "+%zu > %zu", + cnt, idx, he->packet, (size_t) dh->allocsize, + (size_t) db->head->first_free); + } + + run = he->next; + ++cnt; + } + } + } + } + + /* Make sure the data on disk is updated. */ + if (db->persistent) + msync (db->head, db->data + db->head->first_free - (char *) db->head, + MS_ASYNC); + + + /* Now we are done modifying the data. */ + ++db->head->gc_cycle; + assert ((db->head->gc_cycle & 1) == 0); + + /* We are done. */ + out: + pthread_mutex_unlock (&db->memlock); + pthread_rwlock_unlock (&db->lock); + + if (he_use_malloc) + free (he); + if (mark_use_malloc) + free (mark); + + obstack_free (&ob, NULL); +} + + +void * +mempool_alloc (struct database_dyn *db, size_t len, int data_alloc) +{ + /* Make sure LEN is a multiple of our maximum alignment so we can + keep track of used memory is multiples of this alignment value. */ + if ((len & BLOCK_ALIGN_M1) != 0) + len += BLOCK_ALIGN - (len & BLOCK_ALIGN_M1); + + if (data_alloc) + pthread_rwlock_rdlock (&db->lock); + + pthread_mutex_lock (&db->memlock); + + assert ((db->head->first_free & BLOCK_ALIGN_M1) == 0); + + bool tried_resize = false; + void *res; + retry: + res = db->data + db->head->first_free; + + if (__glibc_unlikely (db->head->first_free + len > db->head->data_size)) + { + if (! tried_resize) + { + /* Try to resize the database. Grow size of 1/8th. */ + size_t oldtotal = (sizeof (struct database_pers_head) + + roundup (db->head->module * sizeof (ref_t), + ALIGN) + + db->head->data_size); + size_t new_data_size = (db->head->data_size + + MAX (2 * len, db->head->data_size / 8)); + size_t newtotal = (sizeof (struct database_pers_head) + + roundup (db->head->module * sizeof (ref_t), ALIGN) + + new_data_size); + if (newtotal > db->max_db_size) + { + new_data_size -= newtotal - db->max_db_size; + newtotal = db->max_db_size; + } + + if (db->mmap_used && newtotal > oldtotal + /* We only have to adjust the file size. The new pages + become magically available. */ + && TEMP_FAILURE_RETRY_VAL (posix_fallocate (db->wr_fd, oldtotal, + newtotal + - oldtotal)) == 0) + { + db->head->data_size = new_data_size; + tried_resize = true; + goto retry; + } + } + + if (data_alloc) + pthread_rwlock_unlock (&db->lock); + + if (! db->last_alloc_failed) + { + dbg_log (_("no more memory for database '%s'"), dbnames[db - dbs]); + + db->last_alloc_failed = true; + } + + ++db->head->addfailed; + + /* No luck. */ + res = NULL; + } + else + { + db->head->first_free += len; + + db->last_alloc_failed = false; + + } + + pthread_mutex_unlock (&db->memlock); + + return res; +} diff --git a/REORG.TODO/nscd/netgroupcache.c b/REORG.TODO/nscd/netgroupcache.c new file mode 100644 index 0000000000..cd0c3ea19b --- /dev/null +++ b/REORG.TODO/nscd/netgroupcache.c @@ -0,0 +1,699 @@ +/* Cache handling for netgroup lookup. + Copyright (C) 2011-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/mman.h> + +#include "../inet/netgroup.h" +#include "nscd.h" +#include "dbg_log.h" + +#include <kernel-features.h> + + +/* This is the standard reply in case the service is disabled. */ +static const netgroup_response_header disabled = +{ + .version = NSCD_VERSION, + .found = -1, + .nresults = 0, + .result_len = 0 +}; + +/* This is the struct describing how to write this record. */ +const struct iovec netgroup_iov_disabled = +{ + .iov_base = (void *) &disabled, + .iov_len = sizeof (disabled) +}; + + +/* This is the standard reply in case we haven't found the dataset. */ +static const netgroup_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .nresults = 0, + .result_len = 0 +}; + + +struct dataset +{ + struct datahead head; + netgroup_response_header resp; + char strdata[0]; +}; + +/* Sends a notfound message and prepares a notfound dataset to write to the + cache. Returns true if there was enough memory to allocate the dataset and + returns the dataset in DATASETP, total bytes to write in TOTALP and the + timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the + dataset. */ +static bool +do_notfound (struct database_dyn *db, int fd, request_header *req, + const char *key, struct dataset **datasetp, ssize_t *totalp, + time_t *timeoutp, char **key_copy) +{ + struct dataset *dataset; + ssize_t total; + time_t timeout; + bool cacheable = false; + + total = sizeof (notfound); + timeout = time (NULL) + db->negtimeout; + + if (fd != -1) + TEMP_FAILURE_RETRY (send (fd, ¬found, total, MSG_NOSIGNAL)); + + dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1); + /* If we cannot permanently store the result, so be it. */ + if (dataset != NULL) + { + timeout = datahead_init_neg (&dataset->head, + sizeof (struct dataset) + req->key_len, + total, db->negtimeout); + + /* This is the reply. */ + memcpy (&dataset->resp, ¬found, total); + + /* Copy the key data. */ + memcpy (dataset->strdata, key, req->key_len); + *key_copy = dataset->strdata; + + cacheable = true; + } + *timeoutp = timeout; + *totalp = total; + *datasetp = dataset; + return cacheable; +} + +static time_t +addgetnetgrentX (struct database_dyn *db, int fd, request_header *req, + const char *key, uid_t uid, struct hashentry *he, + struct datahead *dh, struct dataset **resultp) +{ + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key); + else + dbg_log (_("Reloading \"%s\" in netgroup cache!"), key); + } + + static service_user *netgroup_database; + time_t timeout; + struct dataset *dataset; + bool cacheable = false; + ssize_t total; + bool found = false; + + char *key_copy = NULL; + struct __netgrent data; + size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len); + size_t buffilled = sizeof (*dataset); + char *buffer = NULL; + size_t nentries = 0; + size_t group_len = strlen (key) + 1; + struct name_list *first_needed + = alloca (sizeof (struct name_list) + group_len); + + if (netgroup_database == NULL + && __nss_database_lookup ("netgroup", NULL, NULL, &netgroup_database)) + { + /* No such service. */ + cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout, + &key_copy); + goto writeout; + } + + memset (&data, '\0', sizeof (data)); + buffer = xmalloc (buflen); + first_needed->next = first_needed; + memcpy (first_needed->name, key, group_len); + data.needed_groups = first_needed; + + while (data.needed_groups != NULL) + { + /* Add the next group to the list of those which are known. */ + struct name_list *this_group = data.needed_groups->next; + if (this_group == data.needed_groups) + data.needed_groups = NULL; + else + data.needed_groups->next = this_group->next; + this_group->next = data.known_groups; + data.known_groups = this_group; + + union + { + enum nss_status (*f) (const char *, struct __netgrent *); + void *ptr; + } setfct; + + service_user *nip = netgroup_database; + int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr); + while (!no_more) + { + enum nss_status status + = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data)); + + if (status == NSS_STATUS_SUCCESS) + { + found = true; + union + { + enum nss_status (*f) (struct __netgrent *, char *, size_t, + int *); + void *ptr; + } getfct; + getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r"); + if (getfct.f != NULL) + while (1) + { + int e; + status = getfct.f (&data, buffer + buffilled, + buflen - buffilled - req->key_len, &e); + if (status == NSS_STATUS_SUCCESS) + { + if (data.type == triple_val) + { + const char *nhost = data.val.triple.host; + const char *nuser = data.val.triple.user; + const char *ndomain = data.val.triple.domain; + + size_t hostlen = strlen (nhost ?: "") + 1; + size_t userlen = strlen (nuser ?: "") + 1; + size_t domainlen = strlen (ndomain ?: "") + 1; + + if (nhost == NULL || nuser == NULL || ndomain == NULL + || nhost > nuser || nuser > ndomain) + { + const char *last = nhost; + if (last == NULL + || (nuser != NULL && nuser > last)) + last = nuser; + if (last == NULL + || (ndomain != NULL && ndomain > last)) + last = ndomain; + + size_t bufused + = (last == NULL + ? buffilled + : last + strlen (last) + 1 - buffer); + + /* We have to make temporary copies. */ + size_t needed = hostlen + userlen + domainlen; + + if (buflen - req->key_len - bufused < needed) + { + buflen += MAX (buflen, 2 * needed); + /* Save offset in the old buffer. We don't + bother with the NULL check here since + we'll do that later anyway. */ + size_t nhostdiff = nhost - buffer; + size_t nuserdiff = nuser - buffer; + size_t ndomaindiff = ndomain - buffer; + + char *newbuf = xrealloc (buffer, buflen); + /* Fix up the triplet pointers into the new + buffer. */ + nhost = (nhost ? newbuf + nhostdiff + : NULL); + nuser = (nuser ? newbuf + nuserdiff + : NULL); + ndomain = (ndomain ? newbuf + ndomaindiff + : NULL); + buffer = newbuf; + } + + nhost = memcpy (buffer + bufused, + nhost ?: "", hostlen); + nuser = memcpy ((char *) nhost + hostlen, + nuser ?: "", userlen); + ndomain = memcpy ((char *) nuser + userlen, + ndomain ?: "", domainlen); + } + + char *wp = buffer + buffilled; + wp = memmove (wp, nhost ?: "", hostlen); + wp += hostlen; + wp = memmove (wp, nuser ?: "", userlen); + wp += userlen; + wp = memmove (wp, ndomain ?: "", domainlen); + wp += domainlen; + buffilled = wp - buffer; + ++nentries; + } + else + { + /* Check that the group has not been + requested before. */ + struct name_list *runp = data.needed_groups; + if (runp != NULL) + while (1) + { + if (strcmp (runp->name, data.val.group) == 0) + break; + + runp = runp->next; + if (runp == data.needed_groups) + { + runp = NULL; + break; + } + } + + if (runp == NULL) + { + runp = data.known_groups; + while (runp != NULL) + if (strcmp (runp->name, data.val.group) == 0) + break; + else + runp = runp->next; + } + + if (runp == NULL) + { + /* A new group is requested. */ + size_t namelen = strlen (data.val.group) + 1; + struct name_list *newg = alloca (sizeof (*newg) + + namelen); + memcpy (newg->name, data.val.group, namelen); + if (data.needed_groups == NULL) + data.needed_groups = newg->next = newg; + else + { + newg->next = data.needed_groups->next; + data.needed_groups->next = newg; + data.needed_groups = newg; + } + } + } + } + else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE) + { + buflen *= 2; + buffer = xrealloc (buffer, buflen); + } + else if (status == NSS_STATUS_RETURN + || status == NSS_STATUS_NOTFOUND + || status == NSS_STATUS_UNAVAIL) + /* This was either the last one for this group or the + group was empty or the NSS module had an internal + failure. Look at next group if available. */ + break; + } + + enum nss_status (*endfct) (struct __netgrent *); + endfct = __nss_lookup_function (nip, "endnetgrent"); + if (endfct != NULL) + (void) DL_CALL_FCT (*endfct, (&data)); + + break; + } + + no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr, + status, 0); + } + } + + /* No results. Return a failure and write out a notfound record in the + cache. */ + if (!found) + { + cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout, + &key_copy); + goto writeout; + } + + total = buffilled; + + /* Fill in the dataset. */ + dataset = (struct dataset *) buffer; + timeout = datahead_init_pos (&dataset->head, total + req->key_len, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + db->postimeout); + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.nresults = nentries; + dataset->resp.result_len = buffilled - sizeof (*dataset); + + assert (buflen - buffilled >= req->key_len); + key_copy = memcpy (buffer + buffilled, key, req->key_len); + buffilled += req->key_len; + + /* Now we can determine whether on refill we have to create a new + record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (dataset->head.allocsize == dh->allocsize + && dataset->head.recsize == dh->recsize + && memcmp (&dataset->resp, dh->data, + dh->allocsize - offsetof (struct dataset, resp)) == 0) + { + /* The data has not changed. We will just bump the timeout + value. Note that the new record has been allocated on + the stack and need not be freed. */ + dh->timeout = dataset->head.timeout; + dh->ttl = dataset->head.ttl; + ++dh->nreloads; + dataset = (struct dataset *) dh; + + goto out; + } + } + + { + struct dataset *newp + = (struct dataset *) mempool_alloc (db, total + req->key_len, 1); + if (__glibc_likely (newp != NULL)) + { + /* Adjust pointer into the memory block. */ + key_copy = (char *) newp + (key_copy - buffer); + + dataset = memcpy (newp, dataset, total + req->key_len); + cacheable = true; + + if (he != NULL) + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + + if (he == NULL && fd != -1) + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so would + unnecessarily let the receiver wait. */ + writeout: +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && cacheable) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + total + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); +# ifndef __ASSUME_SENDFILE + ssize_t written = +# endif + sendfileall (fd, db->wr_fd, (char *) &dataset->resp + - (char *) db->head, dataset->head.recsize); +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + } + else +#endif + { +#if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE + use_write: +#endif + writeall (fd, &dataset->resp, dataset->head.recsize); + } + } + + if (cacheable) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + total + req->key_len, + MS_ASYNC); + } + + (void) cache_add (req->type, key_copy, req->key_len, &dataset->head, + true, db, uid, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + + out: + free (buffer); + + *resultp = dataset; + + return timeout; +} + + +static time_t +addinnetgrX (struct database_dyn *db, int fd, request_header *req, + char *key, uid_t uid, struct hashentry *he, + struct datahead *dh) +{ + const char *group = key; + key = (char *) rawmemchr (key, '\0') + 1; + size_t group_len = key - group - 1; + const char *host = *key++ ? key : NULL; + if (host != NULL) + key = (char *) rawmemchr (key, '\0') + 1; + const char *user = *key++ ? key : NULL; + if (user != NULL) + key = (char *) rawmemchr (key, '\0') + 1; + const char *domain = *key++ ? key : NULL; + + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"), + group, host ?: "", user ?: "", domain ?: ""); + else + dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"), + group, host ?: "", user ?: "", domain ?: ""); + } + + struct dataset *result = (struct dataset *) cache_search (GETNETGRENT, + group, group_len, + db, uid); + time_t timeout; + if (result != NULL) + timeout = result->head.timeout; + else + { + request_header req_get = + { + .type = GETNETGRENT, + .key_len = group_len + }; + timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL, + &result); + } + + struct indataset + { + struct datahead head; + innetgroup_response_header resp; + } *dataset + = (struct indataset *) mempool_alloc (db, + sizeof (*dataset) + req->key_len, + 1); + struct indataset dataset_mem; + bool cacheable = true; + if (__glibc_unlikely (dataset == NULL)) + { + cacheable = false; + dataset = &dataset_mem; + } + + datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len, + sizeof (innetgroup_response_header), + he == NULL ? 0 : dh->nreloads + 1, result->head.ttl); + /* Set the notfound status and timeout based on the result from + getnetgrent. */ + dataset->head.notfound = result->head.notfound; + dataset->head.timeout = timeout; + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = result->resp.found; + /* Until we find a matching entry the result is 0. */ + dataset->resp.result = 0; + + char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len); + + if (dataset->resp.found) + { + const char *triplets = (const char *) (&result->resp + 1); + + for (nscd_ssize_t i = result->resp.nresults; i > 0; --i) + { + bool success = true; + + /* For the host, user and domain in each triplet, we assume success + if the value is blank because that is how the wildcard entry to + match anything is stored in the netgroup cache. */ + if (host != NULL && *triplets != '\0') + success = strcmp (host, triplets) == 0; + triplets = (const char *) rawmemchr (triplets, '\0') + 1; + + if (success && user != NULL && *triplets != '\0') + success = strcmp (user, triplets) == 0; + triplets = (const char *) rawmemchr (triplets, '\0') + 1; + + if (success && (domain == NULL || *triplets == '\0' + || strcmp (domain, triplets) == 0)) + { + dataset->resp.result = 1; + break; + } + triplets = (const char *) rawmemchr (triplets, '\0') + 1; + } + } + + if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result) + { + /* The data has not changed. We will just bump the timeout + value. Note that the new record has been allocated on + the stack and need not be freed. */ + dh->timeout = timeout; + dh->ttl = dataset->head.ttl; + ++dh->nreloads; + return timeout; + } + + if (he == NULL) + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so would + unnecessarily let the receiver wait. */ + assert (fd != -1); + +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && cacheable) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + sizeof (*dataset) + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); +# ifndef __ASSUME_SENDFILE + ssize_t written = +# endif + sendfileall (fd, db->wr_fd, + (char *) &dataset->resp - (char *) db->head, + sizeof (innetgroup_response_header)); +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + } + else +#endif + { +#if defined HAVE_SENDFILE && !defined __ASSUME_SENDFILE + use_write: +#endif + writeall (fd, &dataset->resp, sizeof (innetgroup_response_header)); + } + } + + if (cacheable) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset) + + req->key_len, + MS_ASYNC); + } + + (void) cache_add (req->type, key_copy, req->key_len, &dataset->head, + true, db, uid, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + + return timeout; +} + + +void +addgetnetgrent (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + struct dataset *ignore; + + addgetnetgrentX (db, fd, req, key, uid, NULL, NULL, &ignore); +} + + +time_t +readdgetnetgrent (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETNETGRENT, + .key_len = he->len + }; + struct dataset *ignore; + + return addgetnetgrentX (db, -1, &req, db->data + he->key, he->owner, he, dh, + &ignore); +} + + +void +addinnetgr (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + addinnetgrX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdinnetgr (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = INNETGR, + .key_len = he->len + }; + + return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} diff --git a/REORG.TODO/nscd/nscd-client.h b/REORG.TODO/nscd/nscd-client.h new file mode 100644 index 0000000000..96170bff1b --- /dev/null +++ b/REORG.TODO/nscd/nscd-client.h @@ -0,0 +1,452 @@ +/* Copyright (c) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +/* This file defines everything that client code should need to + know to talk to the nscd daemon. */ + +#ifndef _NSCD_CLIENT_H +#define _NSCD_CLIENT_H 1 + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <atomic.h> +#include <nscd-types.h> +#include <sys/uio.h> + + +/* Version number of the daemon interface */ +#define NSCD_VERSION 2 + +/* Path of the file where the PID of the running system is stored. */ +#define _PATH_NSCDPID "/var/run/nscd/nscd.pid" + +/* Path for the Unix domain socket. */ +#define _PATH_NSCDSOCKET "/var/run/nscd/socket" + +/* Path for the configuration file. */ +#define _PATH_NSCDCONF "/etc/nscd.conf" + +/* Maximum allowed length for the key. */ +#define MAXKEYLEN 1024 + + +/* Available services. */ +typedef enum +{ + GETPWBYNAME, + GETPWBYUID, + GETGRBYNAME, + GETGRBYGID, + GETHOSTBYNAME, + GETHOSTBYNAMEv6, + GETHOSTBYADDR, + GETHOSTBYADDRv6, + SHUTDOWN, /* Shut the server down. */ + GETSTAT, /* Get the server statistic. */ + INVALIDATE, /* Invalidate one special cache. */ + GETFDPW, + GETFDGR, + GETFDHST, + GETAI, + INITGROUPS, + GETSERVBYNAME, + GETSERVBYPORT, + GETFDSERV, + GETNETGRENT, + INNETGR, + GETFDNETGR, + LASTREQ +} request_type; + + +/* Header common to all requests */ +typedef struct +{ + int32_t version; /* Version number of the daemon interface. */ + request_type type; /* Service requested. */ + int32_t key_len; /* Key length. */ +} request_header; + + +/* Structure sent in reply to password query. Note that this struct is + sent also if the service is disabled or there is no record found. */ +typedef struct +{ + int32_t version; + int32_t found; + nscd_ssize_t pw_name_len; + nscd_ssize_t pw_passwd_len; + uid_t pw_uid; + gid_t pw_gid; + nscd_ssize_t pw_gecos_len; + nscd_ssize_t pw_dir_len; + nscd_ssize_t pw_shell_len; +} pw_response_header; + + +/* Structure sent in reply to group query. Note that this struct is + sent also if the service is disabled or there is no record found. */ +typedef struct +{ + int32_t version; + int32_t found; + nscd_ssize_t gr_name_len; + nscd_ssize_t gr_passwd_len; + gid_t gr_gid; + nscd_ssize_t gr_mem_cnt; +} gr_response_header; + + +/* Structure sent in reply to host query. Note that this struct is + sent also if the service is disabled or there is no record found. */ +typedef struct +{ + int32_t version; + int32_t found; + nscd_ssize_t h_name_len; + nscd_ssize_t h_aliases_cnt; + int32_t h_addrtype; + int32_t h_length; + nscd_ssize_t h_addr_list_cnt; + int32_t error; +} hst_response_header; + + +/* Structure sent in reply to addrinfo query. Note that this struct is + sent also if the service is disabled or there is no record found. */ +typedef struct +{ + int32_t version; + int32_t found; + nscd_ssize_t naddrs; + nscd_ssize_t addrslen; + nscd_ssize_t canonlen; + int32_t error; +} ai_response_header; + +/* Structure filled in by __nscd_getai. */ +struct nscd_ai_result +{ + int naddrs; + char *canon; + uint8_t *family; + char *addrs; +}; + +/* Structure sent in reply to initgroups query. Note that this struct is + sent also if the service is disabled or there is no record found. */ +typedef struct +{ + int32_t version; + int32_t found; + nscd_ssize_t ngrps; +} initgr_response_header; + + +/* Structure sent in reply to services query. Note that this struct is + sent also if the service is disabled or there is no record found. */ +typedef struct +{ + int32_t version; + int32_t found; + nscd_ssize_t s_name_len; + nscd_ssize_t s_proto_len; + nscd_ssize_t s_aliases_cnt; + int32_t s_port; +} serv_response_header; + + +/* Structure send in reply to netgroup query. Note that this struct is + sent also if the service is disabled or there is no record found. */ +typedef struct +{ + int32_t version; + int32_t found; + nscd_ssize_t nresults; + nscd_ssize_t result_len; +} netgroup_response_header; + +typedef struct +{ + int32_t version; + int32_t found; + int32_t result; +} innetgroup_response_header; + + +/* Type for offsets in data part of database. */ +typedef uint32_t ref_t; +/* Value for invalid/no reference. */ +#define ENDREF UINT32_MAX + +/* Timestamp type. */ +typedef uint64_t nscd_time_t; + +/* Maximum timestamp. */ +#define MAX_TIMEOUT_VALUE \ + (sizeof (time_t) == sizeof (long int) ? LONG_MAX : INT_MAX) + +/* Alignment requirement of the beginning of the data region. */ +#define ALIGN 16 + + +/* Head of record in data part of database. */ +struct datahead +{ + nscd_ssize_t allocsize; /* Allocated Bytes. */ + nscd_ssize_t recsize; /* Size of the record. */ + nscd_time_t timeout; /* Time when this entry becomes invalid. */ + uint8_t notfound; /* Nonzero if data has not been found. */ + uint8_t nreloads; /* Reloads without use. */ + uint8_t usable; /* False if the entry must be ignored. */ + uint8_t unused; /* Unused. */ + uint32_t ttl; /* TTL value used. */ + + /* We need to have the following element aligned for the response + header data types and their use in the 'struct dataset' types + defined in the XXXcache.c files. */ + union + { + pw_response_header pwdata; + gr_response_header grdata; + hst_response_header hstdata; + ai_response_header aidata; + initgr_response_header initgrdata; + serv_response_header servdata; + netgroup_response_header netgroupdata; + innetgroup_response_header innetgroupdata; + nscd_ssize_t align1; + nscd_time_t align2; + } data[0]; +}; + +static inline time_t +datahead_init_common (struct datahead *head, nscd_ssize_t allocsize, + nscd_ssize_t recsize, uint32_t ttl) +{ + /* Initialize so that we don't write out junk in uninitialized data to the + cache. */ + memset (head, 0, sizeof (*head)); + + head->allocsize = allocsize; + head->recsize = recsize; + head->usable = true; + + head->ttl = ttl; + + /* Compute and return the timeout time. */ + return head->timeout = time (NULL) + ttl; +} + +static inline time_t +datahead_init_pos (struct datahead *head, nscd_ssize_t allocsize, + nscd_ssize_t recsize, uint8_t nreloads, uint32_t ttl) +{ + time_t ret = datahead_init_common (head, allocsize, recsize, ttl); + + head->notfound = false; + head->nreloads = nreloads; + + return ret; +} + +static inline time_t +datahead_init_neg (struct datahead *head, nscd_ssize_t allocsize, + nscd_ssize_t recsize, uint32_t ttl) +{ + time_t ret = datahead_init_common (head, allocsize, recsize, ttl); + + /* We don't need to touch nreloads here since it is set to our desired value + (0) when we clear the structure. */ + head->notfound = true; + + return ret; +} + +/* Structure for one hash table entry. */ +struct hashentry +{ + request_type type:8; /* Which type of dataset. */ + bool first; /* True if this was the original key. */ + nscd_ssize_t len; /* Length of key. */ + ref_t key; /* Pointer to key. */ + int32_t owner; /* If secure table, this is the owner. */ + ref_t next; /* Next entry in this hash bucket list. */ + ref_t packet; /* Records for the result. */ + union + { + struct hashentry *dellist; /* Next record to be deleted. This can be a + pointer since only nscd uses this field. */ + ref_t *prevp; /* Pointer to field containing forward + reference. */ + }; +}; + + +/* Current persistent database version. */ +#define DB_VERSION 2 + +/* Maximum time allowed between updates of the timestamp. */ +#define MAPPING_TIMEOUT (5 * 60) + + +/* Used indices for the EXTRA_DATA element of 'database_pers_head'. + Each database has its own indices. */ +#define NSCD_HST_IDX_CONF_TIMESTAMP 0 + + +/* Header of persistent database file. */ +struct database_pers_head +{ + int32_t version; + int32_t header_size; + volatile int32_t gc_cycle; + volatile int32_t nscd_certainly_running; + volatile nscd_time_t timestamp; + /* Room for extensions. */ + volatile uint32_t extra_data[4]; + + nscd_ssize_t module; + nscd_ssize_t data_size; + + nscd_ssize_t first_free; /* Offset of first free byte in data area. */ + + nscd_ssize_t nentries; + nscd_ssize_t maxnentries; + nscd_ssize_t maxnsearched; + + uint64_t poshit; + uint64_t neghit; + uint64_t posmiss; + uint64_t negmiss; + + uint64_t rdlockdelayed; + uint64_t wrlockdelayed; + + uint64_t addfailed; + + ref_t array[0]; +}; + + +/* Mapped database record. */ +struct mapped_database +{ + const struct database_pers_head *head; + const char *data; + size_t mapsize; + int counter; /* > 0 indicates it is usable. */ + size_t datasize; +}; +#define NO_MAPPING ((struct mapped_database *) -1l) + +struct locked_map_ptr +{ + int lock; + struct mapped_database *mapped; +}; +#define libc_locked_map_ptr(class, name) class struct locked_map_ptr name + +/* Try acquiring lock for mapptr, returns true if it succeeds, false + if not. */ +static inline bool +__nscd_acquire_maplock (volatile struct locked_map_ptr *mapptr) +{ + int cnt = 0; + while (__builtin_expect (atomic_compare_and_exchange_val_acq (&mapptr->lock, + 1, 0) != 0, 0)) + { + // XXX Best number of rounds? + if (__glibc_unlikely (++cnt > 5)) + return false; + + atomic_spin_nop (); + } + + return true; +} + + +/* Open socket connection to nscd server. */ +extern int __nscd_open_socket (const char *key, size_t keylen, + request_type type, void *response, + size_t responselen) attribute_hidden; + +/* Try to get a file descriptor for the shared meory segment + containing the database. */ +extern struct mapped_database *__nscd_get_mapping (request_type type, + const char *key, + struct mapped_database **mappedp) attribute_hidden; + +/* Get reference of mapping. */ +extern struct mapped_database *__nscd_get_map_ref (request_type type, + const char *name, + volatile struct locked_map_ptr *mapptr, + int *gc_cyclep); + +/* Unmap database. */ +extern void __nscd_unmap (struct mapped_database *mapped); + +/* Drop reference of mapping. */ +static int +__attribute__ ((unused)) +__nscd_drop_map_ref (struct mapped_database *map, int *gc_cycle) +{ + if (map != NO_MAPPING) + { + int now_cycle = map->head->gc_cycle; + if (__glibc_unlikely (now_cycle != *gc_cycle)) + { + /* We might have read inconsistent data. */ + *gc_cycle = now_cycle; + return -1; + } + + if (atomic_decrement_val (&map->counter) == 0) + __nscd_unmap (map); + } + + return 0; +} + + +/* Search the mapped database. */ +extern struct datahead *__nscd_cache_search (request_type type, + const char *key, + size_t keylen, + const struct mapped_database *mapped, + size_t datalen); + +/* Wrappers around read, readv and write that only read/write less than LEN + bytes on error or EOF. */ +extern ssize_t __readall (int fd, void *buf, size_t len) + attribute_hidden; +extern ssize_t __readvall (int fd, const struct iovec *iov, int iovcnt) + attribute_hidden; +extern ssize_t writeall (int fd, const void *buf, size_t len) + attribute_hidden; +extern ssize_t sendfileall (int tofd, int fromfd, off_t off, size_t len) + attribute_hidden; + +/* Get netlink timestamp counter from mapped area or zero. */ +extern uint32_t __nscd_get_nl_timestamp (void); + +#endif /* nscd.h */ diff --git a/REORG.TODO/nscd/nscd.c b/REORG.TODO/nscd/nscd.c new file mode 100644 index 0000000000..69ef41366c --- /dev/null +++ b/REORG.TODO/nscd/nscd.c @@ -0,0 +1,700 @@ +/* Copyright (c) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +/* nscd - Name Service Cache Daemon. Caches passwd, group, and hosts. */ + +#include <argp.h> +#include <assert.h> +#include <dirent.h> +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <libintl.h> +#include <locale.h> +#include <paths.h> +#include <pthread.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <stdarg.h> + +#include "dbg_log.h" +#include "nscd.h" +#include "selinux.h" +#include "../nss/nsswitch.h" +#include <device-nrs.h> +#ifdef HAVE_INOTIFY +# include <sys/inotify.h> +#endif +#include <kernel-features.h> + +/* Get libc version number. */ +#include <version.h> + +#define PACKAGE _libc_intl_domainname + +int do_shutdown; +int disabled_passwd; +int disabled_group; + +typedef enum +{ + /* Running in background as daemon. */ + RUN_DAEMONIZE, + /* Running in foreground but otherwise behave like a daemon, + i.e., detach from terminal and use syslog. This allows + better integration with services like systemd. */ + RUN_FOREGROUND, + /* Run in foreground in debug mode. */ + RUN_DEBUG +} run_modes; + +static run_modes run_mode = RUN_DAEMONIZE; + +static const char *conffile = _PATH_NSCDCONF; + +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); +static int monitor_child (int fd); + +/* Name and version of program. */ +static void print_version (FILE *stream, struct argp_state *state); +void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; + +/* Function to print some extra text in the help message. */ +static char *more_help (int key, const char *text, void *input); + +/* Definitions of arguments for argp functions. */ +static const struct argp_option options[] = +{ + { "config-file", 'f', N_("NAME"), 0, + N_("Read configuration data from NAME") }, + { "debug", 'd', NULL, 0, + N_("Do not fork and display messages on the current tty") }, + { "foreground", 'F', NULL, 0, + N_("Do not fork, but otherwise behave like a daemon") }, + { "nthreads", 't', N_("NUMBER"), 0, N_("Start NUMBER threads") }, + { "shutdown", 'K', NULL, 0, N_("Shut the server down") }, + { "statistics", 'g', NULL, 0, N_("Print current configuration statistics") }, + { "invalidate", 'i', N_("TABLE"), 0, + N_("Invalidate the specified cache") }, + { "secure", 'S', N_("TABLE,yes"), OPTION_HIDDEN, + N_("Use separate cache for each user")}, + { NULL, 0, NULL, 0, NULL } +}; + +/* Short description of program. */ +static const char doc[] = N_("Name Service Cache Daemon."); + +/* Prototype for option handler. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state); + +/* Data structure to communicate with argp functions. */ +static struct argp argp = +{ + options, parse_opt, NULL, doc, NULL, more_help +}; + +/* True if only statistics are requested. */ +static bool get_stats; +static int parent_fd = -1; + +int +main (int argc, char **argv) +{ + int remaining; + + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); + /* Set the text message domain. */ + textdomain (PACKAGE); + + /* Determine if the kernel has SELinux support. */ + nscd_selinux_enabled (&selinux_enabled); + + /* Parse and process arguments. */ + argp_parse (&argp, argc, argv, 0, &remaining, NULL); + + if (remaining != argc) + { + error (0, 0, gettext ("wrong number of arguments")); + argp_help (&argp, stdout, ARGP_HELP_SEE, program_invocation_short_name); + exit (1); + } + + /* Read the configuration file. */ + if (nscd_parse_file (conffile, dbs) != 0) + /* We couldn't read the configuration file. We don't start the + server. */ + error (EXIT_FAILURE, 0, + _("failure while reading configuration file; this is fatal")); + + /* Do we only get statistics? */ + if (get_stats) + /* Does not return. */ + receive_print_stats (); + + /* Check if we are already running. */ + if (check_pid (_PATH_NSCDPID)) + error (EXIT_FAILURE, 0, _("already running")); + + /* Remember when we started. */ + start_time = time (NULL); + + /* Determine page size. */ + pagesize_m1 = getpagesize () - 1; + + if (run_mode == RUN_DAEMONIZE || run_mode == RUN_FOREGROUND) + { + int i; + pid_t pid; + + /* Behave like a daemon. */ + if (run_mode == RUN_DAEMONIZE) + { + int fd[2]; + + if (pipe (fd) != 0) + error (EXIT_FAILURE, errno, + _("cannot create a pipe to talk to the child")); + + pid = fork (); + if (pid == -1) + error (EXIT_FAILURE, errno, _("cannot fork")); + if (pid != 0) + { + /* The parent only reads from the child. */ + close (fd[1]); + exit (monitor_child (fd[0])); + } + else + { + /* The child only writes to the parent. */ + close (fd[0]); + parent_fd = fd[1]; + } + } + + int nullfd = open (_PATH_DEVNULL, O_RDWR); + if (nullfd != -1) + { + struct stat64 st; + + if (fstat64 (nullfd, &st) == 0 && S_ISCHR (st.st_mode) != 0 +#if defined DEV_NULL_MAJOR && defined DEV_NULL_MINOR + && st.st_rdev == makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR) +#endif + ) + { + /* It is the /dev/null special device alright. */ + (void) dup2 (nullfd, STDIN_FILENO); + (void) dup2 (nullfd, STDOUT_FILENO); + (void) dup2 (nullfd, STDERR_FILENO); + + if (nullfd > 2) + close (nullfd); + } + else + { + /* Ugh, somebody is trying to play a trick on us. */ + close (nullfd); + nullfd = -1; + } + } + int min_close_fd = nullfd == -1 ? 0 : STDERR_FILENO + 1; + + DIR *d = opendir ("/proc/self/fd"); + if (d != NULL) + { + struct dirent64 *dirent; + int dfdn = dirfd (d); + + while ((dirent = readdir64 (d)) != NULL) + { + char *endp; + long int fdn = strtol (dirent->d_name, &endp, 10); + + if (*endp == '\0' && fdn != dfdn && fdn >= min_close_fd + && fdn != parent_fd) + close ((int) fdn); + } + + closedir (d); + } + else + for (i = min_close_fd; i < getdtablesize (); i++) + if (i != parent_fd) + close (i); + + setsid (); + + if (chdir ("/") != 0) + do_exit (EXIT_FAILURE, errno, + _("cannot change current working directory to \"/\"")); + + openlog ("nscd", LOG_CONS | LOG_ODELAY, LOG_DAEMON); + + if (write_pid (_PATH_NSCDPID) < 0) + dbg_log ("%s: %s", _PATH_NSCDPID, strerror (errno)); + + if (!init_logfile ()) + dbg_log (_("Could not create log file")); + + /* Ignore job control signals. */ + signal (SIGTTOU, SIG_IGN); + signal (SIGTTIN, SIG_IGN); + signal (SIGTSTP, SIG_IGN); + } + else + /* In debug mode we are not paranoid. */ + paranoia = 0; + + signal (SIGINT, termination_handler); + signal (SIGQUIT, termination_handler); + signal (SIGTERM, termination_handler); + signal (SIGPIPE, SIG_IGN); + + /* Cleanup files created by a previous 'bind'. */ + unlink (_PATH_NSCDSOCKET); + +#ifdef HAVE_INOTIFY + /* Use inotify to recognize changed files. */ + inotify_fd = inotify_init1 (IN_NONBLOCK); +# ifndef __ASSUME_IN_NONBLOCK + if (inotify_fd == -1 && errno == ENOSYS) + { + inotify_fd = inotify_init (); + if (inotify_fd != -1) + fcntl (inotify_fd, F_SETFL, O_RDONLY | O_NONBLOCK); + } +# endif +#endif + +#ifdef USE_NSCD + /* Make sure we do not get recursive calls. */ + __nss_disable_nscd (register_traced_file); +#endif + + /* Init databases. */ + nscd_init (); + + /* Start the SELinux AVC. */ + if (selinux_enabled) + nscd_avc_init (); + + /* Handle incoming requests */ + start_threads (); + + return 0; +} + + +static void __attribute__ ((noreturn)) +invalidate_db (const char *dbname) +{ + int sock = nscd_open_socket (); + + if (sock == -1) + exit (EXIT_FAILURE); + + size_t dbname_len = strlen (dbname) + 1; + size_t reqlen = sizeof (request_header) + dbname_len; + struct + { + request_header req; + char dbname[]; + } *reqdata = alloca (reqlen); + + reqdata->req.key_len = dbname_len; + reqdata->req.version = NSCD_VERSION; + reqdata->req.type = INVALIDATE; + memcpy (reqdata->dbname, dbname, dbname_len); + + ssize_t nbytes = TEMP_FAILURE_RETRY (send (sock, reqdata, reqlen, + MSG_NOSIGNAL)); + + if (nbytes != reqlen) + { + int err = errno; + close (sock); + error (EXIT_FAILURE, err, _("write incomplete")); + } + + /* Wait for ack. Older nscd just closed the socket when + prune_cache finished, silently ignore that. */ + int32_t resp = 0; + nbytes = TEMP_FAILURE_RETRY (read (sock, &resp, sizeof (resp))); + if (nbytes != 0 && nbytes != sizeof (resp)) + { + int err = errno; + close (sock); + error (EXIT_FAILURE, err, _("cannot read invalidate ACK")); + } + + close (sock); + + if (resp != 0) + error (EXIT_FAILURE, resp, _("invalidation failed")); + + exit (0); +} + +static void __attribute__ ((noreturn)) +send_shutdown (void) +{ + int sock = nscd_open_socket (); + + if (sock == -1) + exit (EXIT_FAILURE); + + request_header req; + req.version = NSCD_VERSION; + req.type = SHUTDOWN; + req.key_len = 0; + + ssize_t nbytes = TEMP_FAILURE_RETRY (send (sock, &req, sizeof req, + MSG_NOSIGNAL)); + close (sock); + exit (nbytes != sizeof (request_header) ? EXIT_FAILURE : EXIT_SUCCESS); +} + +/* Handle program arguments. */ +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case 'd': + ++debug_level; + run_mode = RUN_DEBUG; + break; + + case 'F': + run_mode = RUN_FOREGROUND; + break; + + case 'f': + conffile = arg; + break; + + case 'K': + if (getuid () != 0) + error (4, 0, _("Only root is allowed to use this option!")); + else + send_shutdown (); + break; + + case 'g': + get_stats = true; + break; + + case 'i': + { + /* Validate the database name. */ + + dbtype cnt; + for (cnt = pwddb; cnt < lastdb; ++cnt) + if (strcmp (arg, dbnames[cnt]) == 0) + break; + + if (cnt == lastdb) + { + argp_error (state, _("'%s' is not a known database"), arg); + return EINVAL; + } + } + if (getuid () != 0) + error (4, 0, _("Only root is allowed to use this option!")); + else + invalidate_db (arg); + break; + + case 't': + nthreads = atol (arg); + break; + + case 'S': + error (0, 0, _("secure services not implemented anymore")); + break; + + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Print bug-reporting information in the help message. */ +static char * +more_help (int key, const char *text, void *input) +{ + switch (key) + { + case ARGP_KEY_HELP_EXTRA: + { + /* We print some extra information. */ + + char *tables = xstrdup (dbnames[0]); + for (dbtype i = 1; i < lastdb; ++i) + { + char *more_tables; + if (asprintf (&more_tables, "%s %s", tables, dbnames[i]) < 0) + more_tables = NULL; + free (tables); + if (more_tables == NULL) + return NULL; + tables = more_tables; + } + + char *tp; + if (asprintf (&tp, gettext ("\ +Supported tables:\n\ +%s\n\ +\n\ +For bug reporting instructions, please see:\n\ +%s.\n\ +"), tables, REPORT_BUGS_TO) < 0) + tp = NULL; + free (tables); + return tp; + } + + default: + break; + } + + return (char *) text; +} + +/* Print the version information. */ +static void +print_version (FILE *stream, struct argp_state *state) +{ + fprintf (stream, "nscd %s%s\n", PKGVERSION, VERSION); + fprintf (stream, gettext ("\ +Copyright (C) %s Free Software Foundation, Inc.\n\ +This is free software; see the source for copying conditions. There is NO\n\ +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ +"), "2017"); + fprintf (stream, gettext ("Written by %s.\n"), + "Thorsten Kukuk and Ulrich Drepper"); +} + + +/* Create a socket connected to a name. */ +int +nscd_open_socket (void) +{ + struct sockaddr_un addr; + int sock; + + sock = socket (PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + return -1; + + addr.sun_family = AF_UNIX; + assert (sizeof (addr.sun_path) >= sizeof (_PATH_NSCDSOCKET)); + strcpy (addr.sun_path, _PATH_NSCDSOCKET); + if (connect (sock, (struct sockaddr *) &addr, sizeof (addr)) < 0) + { + close (sock); + return -1; + } + + return sock; +} + + +/* Cleanup. */ +void +termination_handler (int signum) +{ + close_sockets (); + + /* Clean up the file created by 'bind'. */ + unlink (_PATH_NSCDSOCKET); + + /* Clean up pid file. */ + unlink (_PATH_NSCDPID); + + // XXX Terminate threads. + + /* Synchronize memory. */ + for (int cnt = 0; cnt < lastdb; ++cnt) + { + if (!dbs[cnt].enabled || dbs[cnt].head == NULL) + continue; + + /* 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); + } + + _exit (EXIT_SUCCESS); +} + +/* Returns 1 if the process in pid file FILE is running, 0 if not. */ +static int +check_pid (const char *file) +{ + FILE *fp; + + fp = fopen (file, "r"); + if (fp) + { + pid_t pid; + int n; + + n = fscanf (fp, "%d", &pid); + fclose (fp); + + /* 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; + } + + return 0; +} + +/* Write the current process id to the file FILE. + Returns 0 if successful, -1 if not. */ +static int +write_pid (const char *file) +{ + FILE *fp; + + fp = fopen (file, "w"); + if (fp == NULL) + return -1; + + fprintf (fp, "%d\n", getpid ()); + + int result = fflush (fp) || ferror (fp) ? -1 : 0; + + fclose (fp); + + return result; +} + +static int +monitor_child (int fd) +{ + int child_ret = 0; + int ret = read (fd, &child_ret, sizeof (child_ret)); + + /* The child terminated with an error, either via exit or some other abnormal + method, like a segfault. */ + if (ret <= 0 || child_ret != 0) + { + int status; + int err = wait (&status); + + if (err < 0) + { + fprintf (stderr, _("'wait' failed\n")); + return 1; + } + + if (WIFEXITED (status)) + { + child_ret = WEXITSTATUS (status); + fprintf (stderr, _("child exited with status %d\n"), child_ret); + } + if (WIFSIGNALED (status)) + { + child_ret = WTERMSIG (status); + fprintf (stderr, _("child terminated by signal %d\n"), child_ret); + } + } + + /* We have the child status, so exit with that code. */ + close (fd); + + return child_ret; +} + +void +do_exit (int child_ret, int errnum, const char *format, ...) +{ + if (parent_fd != -1) + { + int ret __attribute__ ((unused)); + ret = write (parent_fd, &child_ret, sizeof (child_ret)); + assert (ret == sizeof (child_ret)); + close (parent_fd); + } + + if (format != NULL) + { + /* Emulate error() since we don't have a va_list variant for it. */ + va_list argp; + + fflush (stdout); + + fprintf (stderr, "%s: ", program_invocation_name); + + va_start (argp, format); + vfprintf (stderr, format, argp); + va_end (argp); + + fprintf (stderr, ": %s\n", strerror (errnum)); + fflush (stderr); + } + + /* Finally, exit. */ + exit (child_ret); +} + +void +notify_parent (int child_ret) +{ + if (parent_fd == -1) + return; + + int ret __attribute__ ((unused)); + ret = write (parent_fd, &child_ret, sizeof (child_ret)); + assert (ret == sizeof (child_ret)); + close (parent_fd); + parent_fd = -1; +} diff --git a/REORG.TODO/nscd/nscd.conf b/REORG.TODO/nscd/nscd.conf new file mode 100644 index 0000000000..39b875912d --- /dev/null +++ b/REORG.TODO/nscd/nscd.conf @@ -0,0 +1,88 @@ +# +# /etc/nscd.conf +# +# An example Name Service Cache config file. This file is needed by nscd. +# +# Legal entries are: +# +# logfile <file> +# debug-level <level> +# 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> +# negative-time-to-live <service> <time in seconds> +# suggested-size <service> <prime number> +# check-files <service> <yes|no> +# persistent <service> <yes|no> +# shared <service> <yes|no> +# max-db-size <service> <number bytes> +# auto-propagate <service> <yes|no> +# +# Currently supported cache names (services): passwd, group, hosts, services +# + + +# logfile /var/log/nscd.log +# threads 4 +# max-threads 32 +# server-user nobody +# stat-user somebody + debug-level 0 +# reload-count 5 + paranoia no +# restart-interval 3600 + + enable-cache passwd yes + positive-time-to-live passwd 600 + negative-time-to-live passwd 20 + suggested-size passwd 211 + check-files passwd yes + persistent passwd yes + shared passwd yes + max-db-size passwd 33554432 + auto-propagate passwd yes + + enable-cache group yes + positive-time-to-live group 3600 + negative-time-to-live group 60 + suggested-size group 211 + check-files group yes + persistent group yes + shared group yes + max-db-size group 33554432 + auto-propagate group yes + + enable-cache hosts yes + positive-time-to-live hosts 3600 + negative-time-to-live hosts 20 + suggested-size hosts 211 + check-files hosts yes + persistent hosts yes + shared hosts yes + max-db-size hosts 33554432 + + enable-cache services yes + positive-time-to-live services 28800 + negative-time-to-live services 20 + suggested-size services 211 + check-files services yes + persistent services yes + shared services yes + max-db-size services 33554432 + + enable-cache netgroup yes + positive-time-to-live netgroup 28800 + negative-time-to-live netgroup 20 + suggested-size netgroup 211 + check-files netgroup yes + persistent netgroup yes + shared netgroup yes + max-db-size netgroup 33554432 diff --git a/REORG.TODO/nscd/nscd.h b/REORG.TODO/nscd/nscd.h new file mode 100644 index 0000000000..c6b0a3c836 --- /dev/null +++ b/REORG.TODO/nscd/nscd.h @@ -0,0 +1,377 @@ +/* Copyright (c) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#ifndef _NSCD_H +#define _NSCD_H 1 + +#include <pthread.h> +#include <stdbool.h> +#include <time.h> +#include <sys/uio.h> + +/* The declarations for the request and response types are in the file + "nscd-client.h", which should contain everything needed by client + functions. */ +#include "nscd-client.h" + + +/* Handle databases. */ +typedef enum +{ + pwddb, + grpdb, + hstdb, + servdb, + netgrdb, + lastdb +} dbtype; + + +/* Default limit on the number of times a value gets reloaded without + being used in the meantime. NSCD does not throw a value out as + soon as it times out. It tries to reload the value from the + server. Only if the value has not been used for so many rounds it + is removed. */ +#define DEFAULT_RELOAD_LIMIT 5 + + +/* Time before restarting the process in paranoia mode. */ +#define RESTART_INTERVAL (60 * 60) + + +/* Stack size for worker threads. */ +#define NSCD_THREAD_STACKSIZE 1024 * 1024 * (sizeof (void *) / 4) + +/* Maximum size of stack frames we allow the thread to use. We use + 80% of the thread stack size. */ +#define MAX_STACK_USE ((8 * NSCD_THREAD_STACKSIZE) / 10) + +/* Records the file registered per database that when changed + or modified requires invalidating the database. */ +struct traced_file +{ + /* Tracks the last modified time of the traced file. */ + time_t mtime; + /* Support multiple registered files per database. */ + struct traced_file *next; + int call_res_init; + /* Requires Inotify support to do anything useful. */ +#define TRACED_FILE 0 +#define TRACED_DIR 1 + int inotify_descr[2]; +# ifndef PATH_MAX +# define PATH_MAX 1024 +# endif + /* The parent directory is used to scan for creation/deletion. */ + char dname[PATH_MAX]; + /* Just the name of the file with no directory component. */ + char *sfname; + /* The full-path name of the registered file. */ + char fname[]; +}; + +/* Initialize a `struct traced_file`. As input we need the name + of the file, and if invalidation requires calling res_init. + If CRINIT is 1 then res_init will be called after invalidation + or if the traced file is changed in any way, otherwise it will + not. */ +static inline void +init_traced_file(struct traced_file *file, const char *fname, int crinit) +{ + char *dname; + file->mtime = 0; + file->inotify_descr[TRACED_FILE] = -1; + file->inotify_descr[TRACED_DIR] = -1; + strcpy (file->fname, fname); + /* Compute the parent directory name and store a copy. The copy makes + it much faster to add/remove watches while nscd is running instead + of computing this over and over again in a temp buffer. */ + file->dname[0] = '\0'; + dname = strrchr (fname, '/'); + if (dname != NULL) + { + size_t len = (size_t)(dname - fname); + if (len > sizeof (file->dname)) + abort (); + strncpy (file->dname, file->fname, len); + file->dname[len] = '\0'; + } + /* The basename is the name just after the last forward slash. */ + file->sfname = &dname[1]; + file->call_res_init = crinit; +} + +#define define_traced_file(id, filename) \ +static union \ +{ \ + struct traced_file file; \ + char buf[sizeof (struct traced_file) + sizeof (filename)]; \ +} id##_traced_file; + +/* Structure describing dynamic part of one database. */ +struct database_dyn +{ + pthread_rwlock_t lock; + pthread_cond_t prune_cond; + pthread_mutex_t prune_lock; + pthread_mutex_t prune_run_lock; + time_t wakeup_time; + + int enabled; + int check_file; + int clear_cache; + int persistent; + int shared; + int propagate; + struct traced_file *traced_files; + const char *db_filename; + size_t suggested_module; + size_t max_db_size; + + unsigned long int postimeout; /* In seconds. */ + unsigned long int negtimeout; /* In seconds. */ + + int wr_fd; /* Writable file descriptor. */ + int ro_fd; /* Unwritable file descriptor. */ + + const struct iovec *disabled_iov; + + struct database_pers_head *head; + char *data; + size_t memsize; + pthread_mutex_t memlock; + bool mmap_used; + bool last_alloc_failed; +}; + + +/* Paths of the file for the persistent storage. */ +#define _PATH_NSCD_PASSWD_DB "/var/db/nscd/passwd" +#define _PATH_NSCD_GROUP_DB "/var/db/nscd/group" +#define _PATH_NSCD_HOSTS_DB "/var/db/nscd/hosts" +#define _PATH_NSCD_SERVICES_DB "/var/db/nscd/services" +#define _PATH_NSCD_NETGROUP_DB "/var/db/nscd/netgroup" + +/* Path used when not using persistent storage. */ +#define _PATH_NSCD_XYZ_DB_TMP "/var/run/nscd/dbXXXXXX" + +/* Maximum alignment requirement we will encounter. */ +#define BLOCK_ALIGN_LOG 3 +#define BLOCK_ALIGN (1 << BLOCK_ALIGN_LOG) +#define BLOCK_ALIGN_M1 (BLOCK_ALIGN - 1) + +/* Default value for the maximum size of the database files. */ +#define DEFAULT_MAX_DB_SIZE (32 * 1024 * 1024) + +/* Number of bytes of data we initially reserve for each hash table bucket. */ +#define DEFAULT_DATASIZE_PER_BUCKET 1024 + +/* Default module of hash table. */ +#define DEFAULT_SUGGESTED_MODULE 211 + + +/* Number of seconds between two cache pruning runs if we do not have + better information when it is really needed. */ +#define CACHE_PRUNE_INTERVAL 15 + + +/* Global variables. */ +extern struct database_dyn dbs[lastdb] attribute_hidden; +extern const char *const dbnames[lastdb]; +extern const char *const serv2str[LASTREQ]; + +extern const struct iovec pwd_iov_disabled; +extern const struct iovec grp_iov_disabled; +extern const struct iovec hst_iov_disabled; +extern const struct iovec serv_iov_disabled; +extern const struct iovec netgroup_iov_disabled; + + +/* Initial number of threads to run. */ +extern int nthreads; +/* Maximum number of threads to use. */ +extern int max_nthreads; + +/* Inotify descriptor. */ +extern int inotify_fd; + +/* User name to run server processes as. */ +extern const char *server_user; + +/* Name and UID of user who is allowed to request statistics. */ +extern const char *stat_user; +extern uid_t stat_uid; + +/* Time the server was started. */ +extern time_t start_time; + +/* Number of times clients had to wait. */ +extern unsigned long int client_queued; + +/* Maximum needed alignment. */ +extern const size_t block_align; + +/* Number of times a value is reloaded without being used. UINT_MAX + means unlimited. */ +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. */ + +/* Wrapper functions with error checking for standard functions. */ +#include <programs/xmalloc.h> + +/* nscd.c */ +extern void termination_handler (int signum) __attribute__ ((__noreturn__)); +extern int nscd_open_socket (void); +void notify_parent (int child_ret); +void do_exit (int child_ret, int errnum, const char *format, ...); + +/* connections.c */ +extern void nscd_init (void); +extern void register_traced_file (size_t dbidx, struct traced_file *finfo); +#ifdef HAVE_INOTIFY +extern void install_watches (struct traced_file *finfo); +#endif +extern void close_sockets (void); +extern void start_threads (void) __attribute__ ((__noreturn__)); + +/* nscd_conf.c */ +extern int nscd_parse_file (const char *fname, + struct database_dyn dbs[lastdb]); + +/* nscd_stat.c */ +extern void send_stats (int fd, struct database_dyn dbs[lastdb]); +extern int receive_print_stats (void) __attribute__ ((__noreturn__)); + +/* cache.c */ +extern struct datahead *cache_search (request_type, const void *key, + size_t len, struct database_dyn *table, + uid_t owner); +extern int cache_add (int type, const void *key, size_t len, + struct datahead *packet, bool first, + struct database_dyn *table, uid_t owner, + bool prune_wakeup); +extern time_t prune_cache (struct database_dyn *table, time_t now, int fd); + +/* pwdcache.c */ +extern void addpwbyname (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid); +extern void addpwbyuid (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid); +extern time_t readdpwbyname (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); +extern time_t readdpwbyuid (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); + +/* grpcache.c */ +extern void addgrbyname (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid); +extern void addgrbygid (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid); +extern time_t readdgrbyname (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); +extern time_t readdgrbygid (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); + +/* hstcache.c */ +extern void addhstbyname (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid); +extern void addhstbyaddr (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid); +extern void addhstbynamev6 (struct database_dyn *db, int fd, + request_header *req, void *key, uid_t uid); +extern void addhstbyaddrv6 (struct database_dyn *db, int fd, + request_header *req, void *key, uid_t uid); +extern time_t readdhstbyname (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); +extern time_t readdhstbyaddr (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); +extern time_t readdhstbynamev6 (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); +extern time_t readdhstbyaddrv6 (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); + +/* aicache.c */ +extern void addhstai (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid); +extern time_t readdhstai (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); + + +/* initgrcache.c */ +extern void addinitgroups (struct database_dyn *db, int fd, + request_header *req, void *key, uid_t uid); +extern time_t readdinitgroups (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); + +/* servicecache.c */ +extern void addservbyname (struct database_dyn *db, int fd, + request_header *req, void *key, uid_t uid); +extern time_t readdservbyname (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); +extern void addservbyport (struct database_dyn *db, int fd, + request_header *req, void *key, uid_t uid); +extern time_t readdservbyport (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); + +/* netgroupcache.c */ +extern void addinnetgr (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid); +extern time_t readdinnetgr (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); +extern void addgetnetgrent (struct database_dyn *db, int fd, + request_header *req, void *key, uid_t uid); +extern time_t readdgetnetgrent (struct database_dyn *db, struct hashentry *he, + struct datahead *dh); + +/* mem.c */ +extern void *mempool_alloc (struct database_dyn *db, size_t len, + int data_alloc); +extern void gc (struct database_dyn *db); + + +/* nscd_setup_thread.c */ +extern int setup_thread (struct database_dyn *db); + + +/* Special version of TEMP_FAILURE_RETRY for functions returning error + values. */ +#define TEMP_FAILURE_RETRY_VAL(expression) \ + (__extension__ \ + ({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == EINTR); \ + __result; })) + +#endif /* nscd.h */ diff --git a/REORG.TODO/nscd/nscd.init b/REORG.TODO/nscd/nscd.init new file mode 100644 index 0000000000..a882da7d8b --- /dev/null +++ b/REORG.TODO/nscd/nscd.init @@ -0,0 +1,116 @@ +#!/bin/bash +# +# nscd: Starts the Name Switch Cache Daemon +# +# chkconfig: - 30 74 +# description: This is a daemon which handles passwd and group lookups \ +# for running programs and cache the results for the next \ +# query. You should start this daemon if you use \ +# slow naming services like NIS, NIS+, LDAP, or hesiod. +# processname: /usr/sbin/nscd +# config: /etc/nscd.conf +# +### BEGIN INIT INFO +# Provides: nscd +# Required-Start: $syslog +# Default-Stop: 0 1 6 +# Short-Description: Starts the Name Switch Cache Daemon +# Description: This is a daemon which handles passwd and group lookups \ +# for running programs and cache the results for the next \ +# query. You should start this daemon if you use \ +# slow naming services like NIS, NIS+, LDAP, or hesiod. +### END INIT INFO + +# Sanity checks. +[ -f /etc/nscd.conf ] || exit 0 +[ -x /usr/sbin/nscd ] || exit 0 + +# Source function library. +. /etc/init.d/functions + +# nscd does not run on any kernel lower than 2.2.0 because of threading +# problems, so we require that in first place. +case $(uname -r) in + 2.[2-9].*) + # this is okay + ;; + [3-9]*) + # these are of course also okay + ;; + *) + #this is not + exit 1 + ;; +esac + +RETVAL=0 +prog=nscd + +start () { + [ -d /var/run/nscd ] || mkdir /var/run/nscd + [ -d /var/db/nscd ] || mkdir /var/db/nscd + echo -n $"Starting $prog: " + daemon /usr/sbin/nscd + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/nscd + return $RETVAL +} + +stop () { + echo -n $"Stopping $prog: " + /usr/sbin/nscd -K + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + rm -f /var/lock/subsys/nscd + # nscd won't be able to remove these if it is running as + # a non-privileged user + rm -f /var/run/nscd/nscd.pid + rm -f /var/run/nscd/socket + success $"$prog shutdown" + else + failure $"$prog shutdown" + fi + echo + return $RETVAL +} + +restart() { + stop + start +} + +# See how we were called. +case "$1" in + start) + start + RETVAL=$? + ;; + stop) + stop + RETVAL=$? + ;; + status) + status nscd + RETVAL=$? + ;; + restart) + restart + RETVAL=$? + ;; + try-restart | condrestart) + [ -e /var/lock/subsys/nscd ] && restart + RETVAL=$? + ;; + force-reload | reload) + echo -n $"Reloading $prog: " + killproc /usr/sbin/nscd -HUP + RETVAL=$? + echo + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}" + RETVAL=1 + ;; +esac +exit $RETVAL diff --git a/REORG.TODO/nscd/nscd.service b/REORG.TODO/nscd/nscd.service new file mode 100644 index 0000000000..ab38e8f982 --- /dev/null +++ b/REORG.TODO/nscd/nscd.service @@ -0,0 +1,19 @@ +# systemd service file for nscd + +[Unit] +Description=Name Service Cache Daemon + +[Service] +Type=forking +ExecStart=/usr/sbin/nscd +ExecStop=/usr/sbin/nscd --shutdown +ExecReload=/usr/sbin/nscd -i passwd +ExecReload=/usr/sbin/nscd -i group +ExecReload=/usr/sbin/nscd -i hosts +ExecReload=/usr/sbin/nscd -i services +ExecReload=/usr/sbin/nscd -i netgroup +Restart=always +PIDFile=/run/nscd/nscd.pid + +[Install] +WantedBy=multi-user.target diff --git a/REORG.TODO/nscd/nscd.tmpfiles b/REORG.TODO/nscd/nscd.tmpfiles new file mode 100644 index 0000000000..52edbba673 --- /dev/null +++ b/REORG.TODO/nscd/nscd.tmpfiles @@ -0,0 +1,4 @@ +# Configuration to create /run/nscd directory +# Used as part of systemd's tmpfiles + +d /run/nscd 0755 root root diff --git a/REORG.TODO/nscd/nscd_conf.c b/REORG.TODO/nscd/nscd_conf.c new file mode 100644 index 0000000000..9c301ad73d --- /dev/null +++ b/REORG.TODO/nscd/nscd_conf.c @@ -0,0 +1,315 @@ +/* Copyright (c) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <ctype.h> +#include <errno.h> +#include <error.h> +#include <libintl.h> +#include <malloc.h> +#include <pwd.h> +#include <stdio.h> +#include <stdio_ext.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/param.h> +#include <sys/types.h> + +#include "dbg_log.h" +#include "nscd.h" + + +/* Names of the databases. */ +const char *const dbnames[lastdb] = +{ + [pwddb] = "passwd", + [grpdb] = "group", + [hstdb] = "hosts", + [servdb] = "services", + [netgrdb] = "netgroup" +}; + + +static int +find_db (const char *name) +{ + for (int cnt = 0; cnt < lastdb; ++cnt) + if (strcmp (name, dbnames[cnt]) == 0) + return cnt; + + error (0, 0, _("database %s is not supported"), name); + return -1; +} + +int +nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb]) +{ + FILE *fp; + char *line, *cp, *entry, *arg1, *arg2; + size_t len; + int cnt; + const unsigned int initial_error_message_count = error_message_count; + + /* Open the configuration file. */ + fp = fopen (fname, "r"); + if (fp == NULL) + return -1; + + /* The stream is not used by more than one thread. */ + (void) __fsetlocking (fp, FSETLOCKING_BYCALLER); + + line = NULL; + len = 0; + + do + { + ssize_t n = getline (&line, &len, fp); + if (n < 0) + break; + if (line[n - 1] == '\n') + line[n - 1] = '\0'; + + /* Because the file format does not know any form of quoting we + can search forward for the next '#' character and if found + make it terminating the line. */ + *strchrnul (line, '#') = '\0'; + + /* If the line is blank it is ignored. */ + if (line[0] == '\0') + continue; + + entry = line; + while (isspace (*entry) && *entry != '\0') + ++entry; + cp = entry; + while (!isspace (*cp) && *cp != '\0') + ++cp; + arg1 = cp; + ++arg1; + *cp = '\0'; + if (strlen (entry) == 0) + error (0, 0, _("Parse error: %s"), line); + while (isspace (*arg1) && *arg1 != '\0') + ++arg1; + cp = arg1; + while (!isspace (*cp) && *cp != '\0') + ++cp; + arg2 = cp; + ++arg2; + *cp = '\0'; + if (strlen (arg2) > 0) + { + while (isspace (*arg2) && *arg2 != '\0') + ++arg2; + cp = arg2; + while (!isspace (*cp) && *cp != '\0') + ++cp; + *cp = '\0'; + } + + if (strcmp (entry, "positive-time-to-live") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + dbs[idx].postimeout = atol (arg2); + } + else if (strcmp (entry, "negative-time-to-live") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + dbs[idx].negtimeout = atol (arg2); + } + else if (strcmp (entry, "suggested-size") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + dbs[idx].suggested_module + = atol (arg2) ?: DEFAULT_SUGGESTED_MODULE; + } + else if (strcmp (entry, "enable-cache") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + { + if (strcmp (arg2, "no") == 0) + dbs[idx].enabled = 0; + else if (strcmp (arg2, "yes") == 0) + dbs[idx].enabled = 1; + } + } + else if (strcmp (entry, "check-files") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + { + if (strcmp (arg2, "no") == 0) + dbs[idx].check_file = 0; + else if (strcmp (arg2, "yes") == 0) + dbs[idx].check_file = 1; + } + } + else if (strcmp (entry, "max-db-size") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + dbs[idx].max_db_size = atol (arg2) ?: DEFAULT_MAX_DB_SIZE; + } + else if (strcmp (entry, "logfile") == 0) + set_logfile (arg1); + else if (strcmp (entry, "debug-level") == 0) + { + int level = atoi (arg1); + if (level > 0) + debug_level = level; + } + else if (strcmp (entry, "threads") == 0) + { + 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) + error (0, 0, _("Must specify user name for server-user option")); + else + server_user = xstrdup (arg1); + } + else if (strcmp (entry, "stat-user") == 0) + { + if (arg1 == NULL) + error (0, 0, _("Must specify user name for stat-user option")); + else + { + stat_user = xstrdup (arg1); + + struct passwd *pw = getpwnam (stat_user); + if (pw != NULL) + stat_uid = pw->pw_uid; + } + } + else if (strcmp (entry, "persistent") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + { + if (strcmp (arg2, "no") == 0) + dbs[idx].persistent = 0; + else if (strcmp (arg2, "yes") == 0) + dbs[idx].persistent = 1; + } + } + else if (strcmp (entry, "shared") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + { + if (strcmp (arg2, "no") == 0) + dbs[idx].shared = 0; + else if (strcmp (arg2, "yes") == 0) + dbs[idx].shared = 1; + } + } + else if (strcmp (entry, "reload-count") == 0) + { + if (strcasecmp (arg1, "unlimited") == 0) + reload_count = UINT_MAX; + else + { + unsigned long int count = strtoul (arg1, NULL, 0); + if (count > UINT8_MAX - 1) + reload_count = UINT_MAX; + else + reload_count = 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 + error (0, 0, _("Must specify value for restart-interval option")); + } + else if (strcmp (entry, "auto-propagate") == 0) + { + int idx = find_db (arg1); + if (idx >= 0) + { + if (strcmp (arg2, "no") == 0) + dbs[idx].propagate = 0; + else if (strcmp (arg2, "yes") == 0) + dbs[idx].propagate = 1; + } + } + else + error (0, 0, _("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) + { + error (0, 0, _("\ +cannot get current working directory: %s; disabling paranoia mode"), + strerror (errno)); + paranoia = 0; + } + } + + /* Enforce sanity. */ + if (max_nthreads < nthreads) + max_nthreads = nthreads; + + for (cnt = 0; cnt < lastdb; ++cnt) + { + size_t datasize = (sizeof (struct database_pers_head) + + roundup (dbs[cnt].suggested_module + * sizeof (ref_t), ALIGN) + + (dbs[cnt].suggested_module + * DEFAULT_DATASIZE_PER_BUCKET)); + if (datasize > dbs[cnt].max_db_size) + { + error (0, 0, _("maximum file size for %s database too small"), + dbnames[cnt]); + dbs[cnt].max_db_size = datasize; + } + + } + + /* Free the buffer. */ + free (line); + /* Close configuration file. */ + fclose (fp); + + return error_message_count != initial_error_message_count; +} diff --git a/REORG.TODO/nscd/nscd_getai.c b/REORG.TODO/nscd/nscd_getai.c new file mode 100644 index 0000000000..daaf6d68b2 --- /dev/null +++ b/REORG.TODO/nscd/nscd_getai.c @@ -0,0 +1,216 @@ +/* Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2004. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <not-cancel.h> + +#include "nscd-client.h" +#include "nscd_proto.h" + + +/* Define in nscd_gethst_r.c. */ +extern int __nss_not_use_nscd_hosts; + + +/* We use the mapping from nscd_gethst. */ +libc_locked_map_ptr (extern, __hst_map_handle) attribute_hidden; + +/* Defined in nscd_gethst_r.c. */ +extern int __nss_have_localdomain attribute_hidden; + + +int +__nscd_getai (const char *key, struct nscd_ai_result **result, int *h_errnop) +{ + if (__glibc_unlikely (__nss_have_localdomain >= 0)) + { + if (__nss_have_localdomain == 0) + __nss_have_localdomain = getenv ("LOCALDOMAIN") != NULL ? 1 : -1; + if (__nss_have_localdomain > 0) + { + __nss_not_use_nscd_hosts = 1; + return -1; + } + } + + size_t keylen = strlen (key) + 1; + int gc_cycle; + int nretries = 0; + + /* If the mapping is available, try to search there instead of + communicating with the nscd. */ + struct mapped_database *mapped; + mapped = __nscd_get_map_ref (GETFDHST, "hosts", &__hst_map_handle, + &gc_cycle); + + retry:; + struct nscd_ai_result *resultbuf = NULL; + const char *recend = (const char *) ~UINTMAX_C (0); + char *respdata = NULL; + int retval = -1; + int sock = -1; + ai_response_header ai_resp; + + if (mapped != NO_MAPPING) + { + struct datahead *found = __nscd_cache_search (GETAI, key, keylen, + mapped, sizeof ai_resp); + if (found != NULL) + { + respdata = (char *) (&found->data[0].aidata + 1); + ai_resp = found->data[0].aidata; + recend = (const char *) found->data + found->recsize; + /* Now check if we can trust ai_resp fields. If GC is + in progress, it can contain anything. */ + if (mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out; + } + } + } + + /* If we do not have the cache mapped, try to get the data over the + socket. */ + if (respdata == NULL) + { + sock = __nscd_open_socket (key, keylen, GETAI, &ai_resp, + sizeof (ai_resp)); + if (sock == -1) + { + /* nscd not running or wrong version. */ + __nss_not_use_nscd_hosts = 1; + goto out; + } + } + + if (ai_resp.found == 1) + { + size_t datalen = ai_resp.naddrs + ai_resp.addrslen + ai_resp.canonlen; + + /* This check really only affects the case where the data + comes from the mapped cache. */ + if (respdata + datalen > recend) + { + assert (sock == -1); + goto out; + } + + /* Create result. */ + resultbuf = (struct nscd_ai_result *) malloc (sizeof (*resultbuf) + + datalen); + if (resultbuf == NULL) + { + *h_errnop = NETDB_INTERNAL; + goto out_close; + } + + /* Set up the data structure, including pointers. */ + resultbuf->naddrs = ai_resp.naddrs; + resultbuf->addrs = (char *) (resultbuf + 1); + resultbuf->family = (uint8_t *) (resultbuf->addrs + ai_resp.addrslen); + if (ai_resp.canonlen != 0) + resultbuf->canon = (char *) (resultbuf->family + resultbuf->naddrs); + else + resultbuf->canon = NULL; + + if (respdata == NULL) + { + /* Read the data from the socket. */ + if ((size_t) __readall (sock, resultbuf + 1, datalen) == datalen) + { + retval = 0; + *result = resultbuf; + } + else + { + free (resultbuf); + *h_errnop = NETDB_INTERNAL; + } + } + else + { + /* Copy the data in the block. */ + memcpy (resultbuf + 1, respdata, datalen); + + /* Try to detect corrupt databases. */ + if (resultbuf->canon != NULL + && resultbuf->canon[ai_resp.canonlen - 1] != '\0') + /* We cannot use the database. */ + { + if (mapped->head->gc_cycle != gc_cycle) + retval = -2; + else + free (resultbuf); + goto out_close; + } + + retval = 0; + *result = resultbuf; + } + } + else + { + if (__glibc_unlikely (ai_resp.found == -1)) + { + /* The daemon does not cache this database. */ + __nss_not_use_nscd_hosts = 1; + goto out_close; + } + + /* Store the error number. */ + *h_errnop = ai_resp.error; + + /* Set errno to 0 to indicate no error, just no found record. */ + __set_errno (0); + /* Even though we have not found anything, the result is zero. */ + retval = 0; + } + + out_close: + if (sock != -1) + close_not_cancel_no_status (sock); + out: + if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0) + { + /* When we come here this means there has been a GC cycle while we + were looking for the data. This means the data might have been + inconsistent. Retry if possible. */ + if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1) + { + /* nscd is just running gc now. Disable using the mapping. */ + if (atomic_decrement_val (&mapped->counter) == 0) + __nscd_unmap (mapped); + mapped = NO_MAPPING; + } + + if (retval != -1) + { + *result = NULL; + free (resultbuf); + goto retry; + } + } + + return retval; +} diff --git a/REORG.TODO/nscd/nscd_getgr_r.c b/REORG.TODO/nscd/nscd_getgr_r.c new file mode 100644 index 0000000000..87b4552197 --- /dev/null +++ b/REORG.TODO/nscd/nscd_getgr_r.c @@ -0,0 +1,330 @@ +/* Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@uni-paderborn.de>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> +#include <assert.h> +#include <errno.h> +#include <grp.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <not-cancel.h> +#include <_itoa.h> +#include <scratch_buffer.h> + +#include "nscd-client.h" +#include "nscd_proto.h" + +int __nss_not_use_nscd_group; + +static int nscd_getgr_r (const char *key, size_t keylen, request_type type, + struct group *resultbuf, char *buffer, + size_t buflen, struct group **result) + internal_function; + + +int +__nscd_getgrnam_r (const char *name, struct group *resultbuf, char *buffer, + size_t buflen, struct group **result) +{ + return nscd_getgr_r (name, strlen (name) + 1, GETGRBYNAME, resultbuf, + buffer, buflen, result); +} + + +int +__nscd_getgrgid_r (gid_t gid, struct group *resultbuf, char *buffer, + size_t buflen, struct group **result) +{ + char buf[3 * sizeof (gid_t)]; + buf[sizeof (buf) - 1] = '\0'; + char *cp = _itoa_word (gid, buf + sizeof (buf) - 1, 10, 0); + + return nscd_getgr_r (cp, buf + sizeof (buf) - cp, GETGRBYGID, resultbuf, + buffer, buflen, result); +} + + +libc_locked_map_ptr (,__gr_map_handle) attribute_hidden; +/* Note that we only free the structure if necessary. The memory + mapping is not removed since it is not visible to the malloc + handling. */ +libc_freeres_fn (gr_map_free) +{ + if (__gr_map_handle.mapped != NO_MAPPING) + { + void *p = __gr_map_handle.mapped; + __gr_map_handle.mapped = NO_MAPPING; + free (p); + } +} + + +static int +internal_function +nscd_getgr_r (const char *key, size_t keylen, request_type type, + struct group *resultbuf, char *buffer, size_t buflen, + struct group **result) +{ + int gc_cycle; + int nretries = 0; + const uint32_t *len = NULL; + struct scratch_buffer lenbuf; + scratch_buffer_init (&lenbuf); + + /* If the mapping is available, try to search there instead of + communicating with the nscd. */ + struct mapped_database *mapped = __nscd_get_map_ref (GETFDGR, "group", + &__gr_map_handle, + &gc_cycle); + retry:; + const char *gr_name = NULL; + size_t gr_name_len = 0; + int retval = -1; + const char *recend = (const char *) ~UINTMAX_C (0); + gr_response_header gr_resp; + + if (mapped != NO_MAPPING) + { + struct datahead *found = __nscd_cache_search (type, key, keylen, mapped, + sizeof gr_resp); + if (found != NULL) + { + len = (const uint32_t *) (&found->data[0].grdata + 1); + gr_resp = found->data[0].grdata; + gr_name = ((const char *) len + + gr_resp.gr_mem_cnt * sizeof (uint32_t)); + gr_name_len = gr_resp.gr_name_len + gr_resp.gr_passwd_len; + recend = (const char *) found->data + found->recsize; + /* Now check if we can trust gr_resp fields. If GC is + in progress, it can contain anything. */ + if (mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out; + } + + /* The alignment is always sufficient, unless GC is in progress. */ + assert (((uintptr_t) len & (__alignof__ (*len) - 1)) == 0); + } + } + + int sock = -1; + if (gr_name == NULL) + { + sock = __nscd_open_socket (key, keylen, type, &gr_resp, + sizeof (gr_resp)); + if (sock == -1) + { + __nss_not_use_nscd_group = 1; + goto out; + } + } + + /* No value found so far. */ + *result = NULL; + + if (__glibc_unlikely (gr_resp.found == -1)) + { + /* The daemon does not cache this database. */ + __nss_not_use_nscd_group = 1; + goto out_close; + } + + if (gr_resp.found == 1) + { + struct iovec vec[2]; + char *p = buffer; + size_t total_len; + uintptr_t align; + nscd_ssize_t cnt; + + /* Now allocate the buffer the array for the group members. We must + align the pointer. */ + align = ((__alignof__ (char *) - (p - ((char *) 0))) + & (__alignof__ (char *) - 1)); + total_len = (align + (1 + gr_resp.gr_mem_cnt) * sizeof (char *) + + gr_resp.gr_name_len + gr_resp.gr_passwd_len); + if (__glibc_unlikely (buflen < total_len)) + { + no_room: + __set_errno (ERANGE); + retval = ERANGE; + goto out_close; + } + buflen -= total_len; + + p += align; + resultbuf->gr_mem = (char **) p; + p += (1 + gr_resp.gr_mem_cnt) * sizeof (char *); + + /* Set pointers for strings. */ + resultbuf->gr_name = p; + p += gr_resp.gr_name_len; + resultbuf->gr_passwd = p; + p += gr_resp.gr_passwd_len; + + /* Fill in what we know now. */ + resultbuf->gr_gid = gr_resp.gr_gid; + + /* Read the length information, group name, and password. */ + if (gr_name == NULL) + { + /* Handle a simple, usual case: no group members. */ + if (__glibc_likely (gr_resp.gr_mem_cnt == 0)) + { + size_t n = gr_resp.gr_name_len + gr_resp.gr_passwd_len; + if (__builtin_expect (__readall (sock, resultbuf->gr_name, n) + != (ssize_t) n, 0)) + goto out_close; + } + else + { + /* Allocate array to store lengths. */ + if (!scratch_buffer_set_array_size + (&lenbuf, gr_resp.gr_mem_cnt, sizeof (uint32_t))) + goto out_close; + len = lenbuf.data; + + vec[0].iov_base = (void *) len; + vec[0].iov_len = gr_resp.gr_mem_cnt * sizeof (uint32_t); + vec[1].iov_base = resultbuf->gr_name; + vec[1].iov_len = gr_resp.gr_name_len + gr_resp.gr_passwd_len; + total_len = vec[0].iov_len + vec[1].iov_len; + + /* Get this data. */ + size_t n = __readvall (sock, vec, 2); + if (__glibc_unlikely (n != total_len)) + goto out_close; + } + } + else + /* We already have the data. Just copy the group name and + password. */ + memcpy (resultbuf->gr_name, gr_name, + gr_resp.gr_name_len + gr_resp.gr_passwd_len); + + /* Clear the terminating entry. */ + resultbuf->gr_mem[gr_resp.gr_mem_cnt] = NULL; + + /* Prepare reading the group members. */ + total_len = 0; + for (cnt = 0; cnt < gr_resp.gr_mem_cnt; ++cnt) + { + resultbuf->gr_mem[cnt] = p; + total_len += len[cnt]; + p += len[cnt]; + } + + if (__glibc_unlikely (gr_name + gr_name_len + total_len > recend)) + { + /* len array might contain garbage during nscd GC cycle, + retry rather than fail in that case. */ + if (gr_name != NULL && mapped->head->gc_cycle != gc_cycle) + retval = -2; + goto out_close; + } + if (__glibc_unlikely (total_len > buflen)) + { + /* len array might contain garbage during nscd GC cycle, + retry rather than fail in that case. */ + if (gr_name != NULL && mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out_close; + } + else + goto no_room; + } + + retval = 0; + + /* If there are no group members TOTAL_LEN is zero. */ + if (gr_name == NULL) + { + if (total_len > 0 + && __builtin_expect (__readall (sock, resultbuf->gr_mem[0], + total_len) != total_len, 0)) + { + /* The `errno' to some value != ERANGE. */ + __set_errno (ENOENT); + retval = ENOENT; + } + else + *result = resultbuf; + } + else + { + /* Copy the group member names. */ + memcpy (resultbuf->gr_mem[0], gr_name + gr_name_len, total_len); + + /* Try to detect corrupt databases. */ + if (resultbuf->gr_name[gr_name_len - 1] != '\0' + || resultbuf->gr_passwd[gr_resp.gr_passwd_len - 1] != '\0' + || ({for (cnt = 0; cnt < gr_resp.gr_mem_cnt; ++cnt) + if (resultbuf->gr_mem[cnt][len[cnt] - 1] != '\0') + break; + cnt < gr_resp.gr_mem_cnt; })) + { + /* We cannot use the database. */ + retval = mapped->head->gc_cycle != gc_cycle ? -2 : -1; + goto out_close; + } + + *result = resultbuf; + } + } + else + { + /* Set errno to 0 to indicate no error, just no found record. */ + __set_errno (0); + /* Even though we have not found anything, the result is zero. */ + retval = 0; + } + + out_close: + if (sock != -1) + close_not_cancel_no_status (sock); + out: + if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0) + { + /* When we come here this means there has been a GC cycle while we + were looking for the data. This means the data might have been + inconsistent. Retry if possible. */ + if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1) + { + /* nscd is just running gc now. Disable using the mapping. */ + if (atomic_decrement_val (&mapped->counter) == 0) + __nscd_unmap (mapped); + mapped = NO_MAPPING; + } + + if (retval != -1) + goto retry; + } + + scratch_buffer_free (&lenbuf); + + return retval; +} diff --git a/REORG.TODO/nscd/nscd_gethst_r.c b/REORG.TODO/nscd/nscd_gethst_r.c new file mode 100644 index 0000000000..daa708b3d3 --- /dev/null +++ b/REORG.TODO/nscd/nscd_gethst_r.c @@ -0,0 +1,459 @@ +/* Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <errno.h> +#include <resolv/resolv-internal.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <arpa/nameser.h> +#include <not-cancel.h> + +#include "nscd-client.h" +#include "nscd_proto.h" + +int __nss_not_use_nscd_hosts; + +static int nscd_gethst_r (const char *key, size_t keylen, request_type type, + struct hostent *resultbuf, char *buffer, + size_t buflen, struct hostent **result, + int *h_errnop) internal_function; + + +int +__nscd_gethostbyname_r (const char *name, struct hostent *resultbuf, + char *buffer, size_t buflen, struct hostent **result, + int *h_errnop) +{ + request_type reqtype; + + reqtype = res_use_inet6 () ? GETHOSTBYNAMEv6 : GETHOSTBYNAME; + + return nscd_gethst_r (name, strlen (name) + 1, reqtype, resultbuf, + buffer, buflen, result, h_errnop); +} + + +int +__nscd_gethostbyname2_r (const char *name, int af, struct hostent *resultbuf, + char *buffer, size_t buflen, struct hostent **result, + int *h_errnop) +{ + request_type reqtype; + + reqtype = af == AF_INET6 ? GETHOSTBYNAMEv6 : GETHOSTBYNAME; + + return nscd_gethst_r (name, strlen (name) + 1, reqtype, resultbuf, + buffer, buflen, result, h_errnop); +} + + +int +__nscd_gethostbyaddr_r (const void *addr, socklen_t len, int type, + struct hostent *resultbuf, char *buffer, size_t buflen, + struct hostent **result, int *h_errnop) +{ + request_type reqtype; + + if (!((len == INADDRSZ && type == AF_INET) + || (len == IN6ADDRSZ && type == AF_INET6))) + /* LEN and TYPE do not match. */ + return -1; + + reqtype = type == AF_INET6 ? GETHOSTBYADDRv6 : GETHOSTBYADDR; + + return nscd_gethst_r (addr, len, reqtype, resultbuf, buffer, buflen, result, + h_errnop); +} + + +libc_locked_map_ptr (, __hst_map_handle) attribute_hidden; +/* Note that we only free the structure if necessary. The memory + mapping is not removed since it is not visible to the malloc + handling. */ +libc_freeres_fn (hst_map_free) +{ + if (__hst_map_handle.mapped != NO_MAPPING) + { + void *p = __hst_map_handle.mapped; + __hst_map_handle.mapped = NO_MAPPING; + free (p); + } +} + + +uint32_t +__nscd_get_nl_timestamp (void) +{ + uint32_t retval; + if (__nss_not_use_nscd_hosts != 0) + return 0; + + /* __nscd_get_mapping can change hst_map_handle.mapped to NO_MAPPING. + However, __nscd_get_mapping assumes the prior value was not NO_MAPPING. + Thus we have to acquire the lock to prevent this thread from changing + hst_map_handle.mapped to NO_MAPPING while another thread is inside + __nscd_get_mapping. */ + if (!__nscd_acquire_maplock (&__hst_map_handle)) + return 0; + + struct mapped_database *map = __hst_map_handle.mapped; + + if (map == NULL + || (map != NO_MAPPING + && map->head->nscd_certainly_running == 0 + && map->head->timestamp + MAPPING_TIMEOUT < time (NULL))) + map = __nscd_get_mapping (GETFDHST, "hosts", &__hst_map_handle.mapped); + + if (map == NO_MAPPING) + retval = 0; + else + retval = map->head->extra_data[NSCD_HST_IDX_CONF_TIMESTAMP]; + + /* Release the lock. */ + __hst_map_handle.lock = 0; + + return retval; +} + + +int __nss_have_localdomain attribute_hidden; + +static int +internal_function +nscd_gethst_r (const char *key, size_t keylen, request_type type, + struct hostent *resultbuf, char *buffer, size_t buflen, + struct hostent **result, int *h_errnop) +{ + if (__glibc_unlikely (__nss_have_localdomain >= 0)) + { + if (__nss_have_localdomain == 0) + __nss_have_localdomain = getenv ("LOCALDOMAIN") != NULL ? 1 : -1; + if (__nss_have_localdomain > 0) + { + __nss_not_use_nscd_hosts = 1; + return -1; + } + } + + int gc_cycle; + int nretries = 0; + + /* If the mapping is available, try to search there instead of + communicating with the nscd. */ + struct mapped_database *mapped; + mapped = __nscd_get_map_ref (GETFDHST, "hosts", &__hst_map_handle, + &gc_cycle); + + retry:; + const char *h_name = NULL; + const uint32_t *aliases_len = NULL; + const char *addr_list = NULL; + size_t addr_list_len = 0; + int retval = -1; + const char *recend = (const char *) ~UINTMAX_C (0); + int sock = -1; + hst_response_header hst_resp; + if (mapped != NO_MAPPING) + { + /* No const qualifier, as it can change during garbage collection. */ + struct datahead *found = __nscd_cache_search (type, key, keylen, mapped, + sizeof hst_resp); + if (found != NULL) + { + h_name = (char *) (&found->data[0].hstdata + 1); + hst_resp = found->data[0].hstdata; + aliases_len = (uint32_t *) (h_name + hst_resp.h_name_len); + addr_list = ((char *) aliases_len + + hst_resp.h_aliases_cnt * sizeof (uint32_t)); + addr_list_len = hst_resp.h_addr_list_cnt * INADDRSZ; + recend = (const char *) found->data + found->recsize; + /* Now check if we can trust hst_resp fields. If GC is + in progress, it can contain anything. */ + if (mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out; + } + +#if !_STRING_ARCH_unaligned + /* The aliases_len array in the mapped database might very + well be unaligned. We will access it word-wise so on + platforms which do not tolerate unaligned accesses we + need to make an aligned copy. */ + if (((uintptr_t) aliases_len & (__alignof__ (*aliases_len) - 1)) + != 0) + { + uint32_t *tmp = alloca (hst_resp.h_aliases_cnt + * sizeof (uint32_t)); + aliases_len = memcpy (tmp, aliases_len, + hst_resp.h_aliases_cnt + * sizeof (uint32_t)); + } +#endif + if (type != GETHOSTBYADDR && type != GETHOSTBYNAME) + { + if (hst_resp.h_length == INADDRSZ) + addr_list += addr_list_len; + addr_list_len = hst_resp.h_addr_list_cnt * IN6ADDRSZ; + } + if (__builtin_expect ((const char *) addr_list + addr_list_len + > recend, 0)) + goto out; + } + } + + if (h_name == NULL) + { + sock = __nscd_open_socket (key, keylen, type, &hst_resp, + sizeof (hst_resp)); + if (sock == -1) + { + __nss_not_use_nscd_hosts = 1; + goto out; + } + } + + /* No value found so far. */ + *result = NULL; + + if (__glibc_unlikely (hst_resp.found == -1)) + { + /* The daemon does not cache this database. */ + __nss_not_use_nscd_hosts = 1; + goto out_close; + } + + if (hst_resp.found == 1) + { + char *cp = buffer; + uintptr_t align1; + uintptr_t align2; + size_t total_len; + ssize_t cnt; + char *ignore; + int n; + + /* A first check whether the buffer is sufficiently large is possible. */ + /* Now allocate the buffer the array for the group members. We must + align the pointer and the base of the h_addr_list pointers. */ + align1 = ((__alignof__ (char *) - (cp - ((char *) 0))) + & (__alignof__ (char *) - 1)); + align2 = ((__alignof__ (char *) - ((cp + align1 + hst_resp.h_name_len) + - ((char *) 0))) + & (__alignof__ (char *) - 1)); + if (buflen < (align1 + hst_resp.h_name_len + align2 + + ((hst_resp.h_aliases_cnt + hst_resp.h_addr_list_cnt + + 2) + * sizeof (char *)) + + hst_resp.h_addr_list_cnt * (type == AF_INET + ? INADDRSZ : IN6ADDRSZ))) + { + no_room: + *h_errnop = NETDB_INTERNAL; + __set_errno (ERANGE); + retval = ERANGE; + goto out_close; + } + cp += align1; + + /* Prepare the result as far as we can. */ + resultbuf->h_aliases = (char **) cp; + cp += (hst_resp.h_aliases_cnt + 1) * sizeof (char *); + resultbuf->h_addr_list = (char **) cp; + cp += (hst_resp.h_addr_list_cnt + 1) * sizeof (char *); + + resultbuf->h_name = cp; + cp += hst_resp.h_name_len + align2; + + if (type == GETHOSTBYADDR || type == GETHOSTBYNAME) + { + resultbuf->h_addrtype = AF_INET; + resultbuf->h_length = INADDRSZ; + } + else + { + resultbuf->h_addrtype = AF_INET6; + resultbuf->h_length = IN6ADDRSZ; + } + for (cnt = 0; cnt < hst_resp.h_addr_list_cnt; ++cnt) + { + resultbuf->h_addr_list[cnt] = cp; + cp += resultbuf->h_length; + } + resultbuf->h_addr_list[cnt] = NULL; + + if (h_name == NULL) + { + struct iovec vec[4]; + + vec[0].iov_base = resultbuf->h_name; + vec[0].iov_len = hst_resp.h_name_len; + total_len = hst_resp.h_name_len; + n = 1; + + if (hst_resp.h_aliases_cnt > 0) + { + aliases_len = alloca (hst_resp.h_aliases_cnt + * sizeof (uint32_t)); + vec[n].iov_base = (void *) aliases_len; + vec[n].iov_len = hst_resp.h_aliases_cnt * sizeof (uint32_t); + + total_len += hst_resp.h_aliases_cnt * sizeof (uint32_t); + ++n; + } + + if (type == GETHOSTBYADDR || type == GETHOSTBYNAME) + { + vec[n].iov_base = resultbuf->h_addr_list[0]; + vec[n].iov_len = hst_resp.h_addr_list_cnt * INADDRSZ; + + total_len += hst_resp.h_addr_list_cnt * INADDRSZ; + + ++n; + } + else + { + if (hst_resp.h_length == INADDRSZ) + { + ignore = alloca (hst_resp.h_addr_list_cnt * INADDRSZ); + vec[n].iov_base = ignore; + vec[n].iov_len = hst_resp.h_addr_list_cnt * INADDRSZ; + + total_len += hst_resp.h_addr_list_cnt * INADDRSZ; + + ++n; + } + + vec[n].iov_base = resultbuf->h_addr_list[0]; + vec[n].iov_len = hst_resp.h_addr_list_cnt * IN6ADDRSZ; + + total_len += hst_resp.h_addr_list_cnt * IN6ADDRSZ; + + ++n; + } + + if ((size_t) __readvall (sock, vec, n) != total_len) + goto out_close; + } + else + { + memcpy (resultbuf->h_name, h_name, hst_resp.h_name_len); + memcpy (resultbuf->h_addr_list[0], addr_list, addr_list_len); + } + + /* Now we also can read the aliases. */ + total_len = 0; + for (cnt = 0; cnt < hst_resp.h_aliases_cnt; ++cnt) + { + resultbuf->h_aliases[cnt] = cp; + cp += aliases_len[cnt]; + total_len += aliases_len[cnt]; + } + resultbuf->h_aliases[cnt] = NULL; + + if (__builtin_expect ((const char *) addr_list + addr_list_len + + total_len > recend, 0)) + { + /* aliases_len array might contain garbage during nscd GC cycle, + retry rather than fail in that case. */ + if (addr_list != NULL && mapped->head->gc_cycle != gc_cycle) + retval = -2; + goto out_close; + } + /* See whether this would exceed the buffer capacity. */ + if (__glibc_unlikely (cp > buffer + buflen)) + { + /* aliases_len array might contain garbage during nscd GC cycle, + retry rather than fail in that case. */ + if (addr_list != NULL && mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out_close; + } + goto no_room; + } + + /* And finally read the aliases. */ + if (addr_list == NULL) + { + if (total_len == 0 + || ((size_t) __readall (sock, resultbuf->h_aliases[0], total_len) + == total_len)) + { + retval = 0; + *result = resultbuf; + } + } + else + { + memcpy (resultbuf->h_aliases[0], + (const char *) addr_list + addr_list_len, total_len); + + /* Try to detect corrupt databases. */ + if (resultbuf->h_name[hst_resp.h_name_len - 1] != '\0' + || ({for (cnt = 0; cnt < hst_resp.h_aliases_cnt; ++cnt) + if (resultbuf->h_aliases[cnt][aliases_len[cnt] - 1] + != '\0') + break; + cnt < hst_resp.h_aliases_cnt; })) + { + /* We cannot use the database. */ + if (mapped->head->gc_cycle != gc_cycle) + retval = -2; + goto out_close; + } + + retval = 0; + *result = resultbuf; + } + } + else + { + /* Store the error number. */ + *h_errnop = hst_resp.error; + + /* Set errno to 0 to indicate no error, just no found record. */ + __set_errno (0); + /* Even though we have not found anything, the result is zero. */ + retval = 0; + } + + out_close: + if (sock != -1) + close_not_cancel_no_status (sock); + out: + if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0) + { + /* When we come here this means there has been a GC cycle while we + were looking for the data. This means the data might have been + inconsistent. Retry if possible. */ + if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1) + { + /* nscd is just running gc now. Disable using the mapping. */ + if (atomic_decrement_val (&mapped->counter) == 0) + __nscd_unmap (mapped); + mapped = NO_MAPPING; + } + + if (retval != -1) + goto retry; + } + + return retval; +} diff --git a/REORG.TODO/nscd/nscd_getpw_r.c b/REORG.TODO/nscd/nscd_getpw_r.c new file mode 100644 index 0000000000..b291d2fa44 --- /dev/null +++ b/REORG.TODO/nscd/nscd_getpw_r.c @@ -0,0 +1,241 @@ +/* Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@uni-paderborn.de>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <pwd.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <not-cancel.h> +#include <_itoa.h> + +#include "nscd-client.h" +#include "nscd_proto.h" + +int __nss_not_use_nscd_passwd; + +static int nscd_getpw_r (const char *key, size_t keylen, request_type type, + struct passwd *resultbuf, char *buffer, + size_t buflen, struct passwd **result) + internal_function; + +int +__nscd_getpwnam_r (const char *name, struct passwd *resultbuf, char *buffer, + size_t buflen, struct passwd **result) +{ + if (name == NULL) + return -1; + + return nscd_getpw_r (name, strlen (name) + 1, GETPWBYNAME, resultbuf, + buffer, buflen, result); +} + +int +__nscd_getpwuid_r (uid_t uid, struct passwd *resultbuf, char *buffer, + size_t buflen, struct passwd **result) +{ + char buf[3 * sizeof (uid_t)]; + buf[sizeof (buf) - 1] = '\0'; + char *cp = _itoa_word (uid, buf + sizeof (buf) - 1, 10, 0); + + return nscd_getpw_r (cp, buf + sizeof (buf) - cp, GETPWBYUID, resultbuf, + buffer, buflen, result); +} + + +libc_locked_map_ptr (static, map_handle); +/* Note that we only free the structure if necessary. The memory + mapping is not removed since it is not visible to the malloc + handling. */ +libc_freeres_fn (pw_map_free) +{ + if (map_handle.mapped != NO_MAPPING) + { + void *p = map_handle.mapped; + map_handle.mapped = NO_MAPPING; + free (p); + } +} + + +static int +internal_function +nscd_getpw_r (const char *key, size_t keylen, request_type type, + struct passwd *resultbuf, char *buffer, size_t buflen, + struct passwd **result) +{ + int gc_cycle; + int nretries = 0; + + /* If the mapping is available, try to search there instead of + communicating with the nscd. */ + struct mapped_database *mapped; + mapped = __nscd_get_map_ref (GETFDPW, "passwd", &map_handle, &gc_cycle); + + retry:; + const char *pw_name = NULL; + int retval = -1; + const char *recend = (const char *) ~UINTMAX_C (0); + pw_response_header pw_resp; + + if (mapped != NO_MAPPING) + { + struct datahead *found = __nscd_cache_search (type, key, keylen, mapped, + sizeof pw_resp); + if (found != NULL) + { + pw_name = (const char *) (&found->data[0].pwdata + 1); + pw_resp = found->data[0].pwdata; + recend = (const char *) found->data + found->recsize; + /* Now check if we can trust pw_resp fields. If GC is + in progress, it can contain anything. */ + if (mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out; + } + } + } + + int sock = -1; + if (pw_name == NULL) + { + sock = __nscd_open_socket (key, keylen, type, &pw_resp, + sizeof (pw_resp)); + if (sock == -1) + { + __nss_not_use_nscd_passwd = 1; + goto out; + } + } + + /* No value found so far. */ + *result = NULL; + + if (__glibc_unlikely (pw_resp.found == -1)) + { + /* The daemon does not cache this database. */ + __nss_not_use_nscd_passwd = 1; + goto out_close; + } + + if (pw_resp.found == 1) + { + /* Set the information we already have. */ + resultbuf->pw_uid = pw_resp.pw_uid; + resultbuf->pw_gid = pw_resp.pw_gid; + + char *p = buffer; + /* get pw_name */ + resultbuf->pw_name = p; + p += pw_resp.pw_name_len; + /* get pw_passwd */ + resultbuf->pw_passwd = p; + p += pw_resp.pw_passwd_len; + /* get pw_gecos */ + resultbuf->pw_gecos = p; + p += pw_resp.pw_gecos_len; + /* get pw_dir */ + resultbuf->pw_dir = p; + p += pw_resp.pw_dir_len; + /* get pw_pshell */ + resultbuf->pw_shell = p; + p += pw_resp.pw_shell_len; + + ssize_t total = p - buffer; + if (__glibc_unlikely (pw_name + total > recend)) + goto out_close; + if (__glibc_unlikely (buflen < total)) + { + __set_errno (ERANGE); + retval = ERANGE; + goto out_close; + } + + retval = 0; + if (pw_name == NULL) + { + ssize_t nbytes = __readall (sock, buffer, total); + + if (__glibc_unlikely (nbytes != total)) + { + /* The `errno' to some value != ERANGE. */ + __set_errno (ENOENT); + retval = ENOENT; + } + else + *result = resultbuf; + } + else + { + /* Copy the various strings. */ + memcpy (resultbuf->pw_name, pw_name, total); + + /* Try to detect corrupt databases. */ + if (resultbuf->pw_name[pw_resp.pw_name_len - 1] != '\0' + || resultbuf->pw_passwd[pw_resp.pw_passwd_len - 1] != '\0' + || resultbuf->pw_gecos[pw_resp.pw_gecos_len - 1] != '\0' + || resultbuf->pw_dir[pw_resp.pw_dir_len - 1] != '\0' + || resultbuf->pw_shell[pw_resp.pw_shell_len - 1] != '\0') + { + /* We cannot use the database. */ + retval = mapped->head->gc_cycle != gc_cycle ? -2 : -1; + goto out_close; + } + + *result = resultbuf; + } + } + else + { + /* Set errno to 0 to indicate no error, just no found record. */ + __set_errno (0); + /* Even though we have not found anything, the result is zero. */ + retval = 0; + } + + out_close: + if (sock != -1) + close_not_cancel_no_status (sock); + out: + if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0) + { + /* When we come here this means there has been a GC cycle while we + were looking for the data. This means the data might have been + inconsistent. Retry if possible. */ + if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1) + { + /* nscd is just running gc now. Disable using the mapping. */ + if (atomic_decrement_val (&mapped->counter) == 0) + __nscd_unmap (mapped); + mapped = NO_MAPPING; + } + + if (retval != -1) + goto retry; + } + + return retval; +} diff --git a/REORG.TODO/nscd/nscd_getserv_r.c b/REORG.TODO/nscd/nscd_getserv_r.c new file mode 100644 index 0000000000..7dfb1c828d --- /dev/null +++ b/REORG.TODO/nscd/nscd_getserv_r.c @@ -0,0 +1,388 @@ +/* Copyright (C) 2007-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2007. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <string.h> +#include <not-cancel.h> +#include <_itoa.h> +#include <stdint.h> + +#include "nscd-client.h" +#include "nscd_proto.h" + + +int __nss_not_use_nscd_services; + + +static int nscd_getserv_r (const char *crit, size_t critlen, const char *proto, + request_type type, struct servent *resultbuf, + char *buf, size_t buflen, struct servent **result); + + +int +__nscd_getservbyname_r (const char *name, const char *proto, + struct servent *result_buf, char *buf, size_t buflen, + struct servent **result) +{ + return nscd_getserv_r (name, strlen (name), proto, GETSERVBYNAME, result_buf, + buf, buflen, result); +} + + +int +__nscd_getservbyport_r (int port, const char *proto, + struct servent *result_buf, char *buf, size_t buflen, + struct servent **result) +{ + char portstr[3 * sizeof (int) + 2]; + portstr[sizeof (portstr) - 1] = '\0'; + char *cp = _itoa_word (port, portstr + sizeof (portstr) - 1, 10, 0); + + return nscd_getserv_r (cp, portstr + sizeof (portstr) - 1 - cp, proto, + GETSERVBYPORT, result_buf, buf, buflen, result); +} + + +libc_locked_map_ptr (, __serv_map_handle) attribute_hidden; +/* Note that we only free the structure if necessary. The memory + mapping is not removed since it is not visible to the malloc + handling. */ +libc_freeres_fn (serv_map_free) +{ + if (__serv_map_handle.mapped != NO_MAPPING) + { + void *p = __serv_map_handle.mapped; + __serv_map_handle.mapped = NO_MAPPING; + free (p); + } +} + + +static int +nscd_getserv_r (const char *crit, size_t critlen, const char *proto, + request_type type, struct servent *resultbuf, + char *buf, size_t buflen, struct servent **result) +{ + int gc_cycle; + int nretries = 0; + size_t alloca_used = 0; + + /* If the mapping is available, try to search there instead of + communicating with the nscd. */ + struct mapped_database *mapped; + mapped = __nscd_get_map_ref (GETFDSERV, "services", &__serv_map_handle, + &gc_cycle); + size_t protolen = proto == NULL ? 0 : strlen (proto); + size_t keylen = critlen + 1 + protolen + 1; + int alloca_key = __libc_use_alloca (keylen); + char *key; + if (alloca_key) + key = alloca_account (keylen, alloca_used); + else + { + key = malloc (keylen); + if (key == NULL) + return -1; + } + memcpy (__mempcpy (__mempcpy (key, crit, critlen), + "/", 1), proto ?: "", protolen + 1); + + retry:; + const char *s_name = NULL; + const char *s_proto = NULL; + int alloca_aliases_len = 0; + const uint32_t *aliases_len = NULL; + const char *aliases_list = NULL; + int retval = -1; + const char *recend = (const char *) ~UINTMAX_C (0); + int sock = -1; + serv_response_header serv_resp; + + if (mapped != NO_MAPPING) + { + struct datahead *found = __nscd_cache_search (type, key, keylen, mapped, + sizeof serv_resp); + + if (found != NULL) + { + s_name = (char *) (&found->data[0].servdata + 1); + serv_resp = found->data[0].servdata; + s_proto = s_name + serv_resp.s_name_len; + alloca_aliases_len = 1; + aliases_len = (uint32_t *) (s_proto + serv_resp.s_proto_len); + aliases_list = ((char *) aliases_len + + serv_resp.s_aliases_cnt * sizeof (uint32_t)); + recend = (const char *) found->data + found->recsize; + /* Now check if we can trust serv_resp fields. If GC is + in progress, it can contain anything. */ + if (mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out; + } + if (__builtin_expect ((const char *) aliases_len + + serv_resp.s_aliases_cnt * sizeof (uint32_t) + > recend, 0)) + goto out; + +#if !_STRING_ARCH_unaligned + /* The aliases_len array in the mapped database might very + well be unaligned. We will access it word-wise so on + platforms which do not tolerate unaligned accesses we + need to make an aligned copy. */ + if (((uintptr_t) aliases_len & (__alignof__ (*aliases_len) - 1)) + != 0) + { + uint32_t *tmp; + alloca_aliases_len + = __libc_use_alloca (alloca_used + + (serv_resp.s_aliases_cnt + * sizeof (uint32_t))); + if (alloca_aliases_len) + tmp = alloca_account (serv_resp.s_aliases_cnt + * sizeof (uint32_t), + alloca_used); + else + { + tmp = malloc (serv_resp.s_aliases_cnt * sizeof (uint32_t)); + if (tmp == NULL) + { + retval = ENOMEM; + goto out; + } + } + aliases_len = memcpy (tmp, aliases_len, + serv_resp.s_aliases_cnt + * sizeof (uint32_t)); + } +#endif + } + } + + if (s_name == NULL) + { + sock = __nscd_open_socket (key, keylen, type, &serv_resp, + sizeof (serv_resp)); + if (sock == -1) + { + __nss_not_use_nscd_services = 1; + goto out; + } + } + + /* No value found so far. */ + *result = NULL; + + if (__glibc_unlikely (serv_resp.found == -1)) + { + /* The daemon does not cache this database. */ + __nss_not_use_nscd_services = 1; + goto out_close; + } + + if (serv_resp.found == 1) + { + char *cp = buf; + uintptr_t align1; + uintptr_t align2; + size_t total_len; + ssize_t cnt; + int n; + + /* A first check whether the buffer is sufficiently large is possible. */ + /* Now allocate the buffer the array for the group members. We must + align the pointer and the base of the h_addr_list pointers. */ + align1 = ((__alignof__ (char *) - (cp - ((char *) 0))) + & (__alignof__ (char *) - 1)); + align2 = ((__alignof__ (char *) - ((cp + align1 + serv_resp.s_name_len + + serv_resp.s_proto_len) + - ((char *) 0))) + & (__alignof__ (char *) - 1)); + if (buflen < (align1 + serv_resp.s_name_len + serv_resp.s_proto_len + + align2 + + (serv_resp.s_aliases_cnt + 1) * sizeof (char *))) + { + no_room: + __set_errno (ERANGE); + retval = ERANGE; + goto out_close; + } + cp += align1; + + /* Prepare the result as far as we can. */ + resultbuf->s_aliases = (char **) cp; + cp += (serv_resp.s_aliases_cnt + 1) * sizeof (char *); + + resultbuf->s_name = cp; + cp += serv_resp.s_name_len; + resultbuf->s_proto = cp; + cp += serv_resp.s_proto_len + align2; + resultbuf->s_port = serv_resp.s_port; + + if (s_name == NULL) + { + struct iovec vec[2]; + + vec[0].iov_base = resultbuf->s_name; + vec[0].iov_len = serv_resp.s_name_len + serv_resp.s_proto_len; + total_len = vec[0].iov_len; + n = 1; + + if (serv_resp.s_aliases_cnt > 0) + { + assert (alloca_aliases_len == 0); + alloca_aliases_len + = __libc_use_alloca (alloca_used + + (serv_resp.s_aliases_cnt + * sizeof (uint32_t))); + if (alloca_aliases_len) + aliases_len = alloca_account (serv_resp.s_aliases_cnt + * sizeof (uint32_t), + alloca_used); + else + { + aliases_len = malloc (serv_resp.s_aliases_cnt + * sizeof (uint32_t)); + if (aliases_len == NULL) + { + retval = ENOMEM; + goto out_close; + } + } + vec[n].iov_base = (void *) aliases_len; + vec[n].iov_len = serv_resp.s_aliases_cnt * sizeof (uint32_t); + + total_len += serv_resp.s_aliases_cnt * sizeof (uint32_t); + ++n; + } + + if ((size_t) __readvall (sock, vec, n) != total_len) + goto out_close; + } + else + memcpy (resultbuf->s_name, s_name, + serv_resp.s_name_len + serv_resp.s_proto_len); + + /* Now we also can read the aliases. */ + total_len = 0; + for (cnt = 0; cnt < serv_resp.s_aliases_cnt; ++cnt) + { + resultbuf->s_aliases[cnt] = cp; + cp += aliases_len[cnt]; + total_len += aliases_len[cnt]; + } + resultbuf->s_aliases[cnt] = NULL; + + if (__builtin_expect ((const char *) aliases_list + total_len > recend, + 0)) + { + /* aliases_len array might contain garbage during nscd GC cycle, + retry rather than fail in that case. */ + if (aliases_list != NULL && mapped->head->gc_cycle != gc_cycle) + retval = -2; + goto out_close; + } + + /* See whether this would exceed the buffer capacity. */ + if (__glibc_unlikely (cp > buf + buflen)) + { + /* aliases_len array might contain garbage during nscd GC cycle, + retry rather than fail in that case. */ + if (aliases_list != NULL && mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out_close; + } + goto no_room; + } + + /* And finally read the aliases. */ + if (aliases_list == NULL) + { + if (total_len == 0 + || ((size_t) __readall (sock, resultbuf->s_aliases[0], total_len) + == total_len)) + { + retval = 0; + *result = resultbuf; + } + } + else + { + memcpy (resultbuf->s_aliases[0], aliases_list, total_len); + + /* Try to detect corrupt databases. */ + if (resultbuf->s_name[serv_resp.s_name_len - 1] != '\0' + || resultbuf->s_proto[serv_resp.s_proto_len - 1] != '\0' + || ({for (cnt = 0; cnt < serv_resp.s_aliases_cnt; ++cnt) + if (resultbuf->s_aliases[cnt][aliases_len[cnt] - 1] + != '\0') + break; + cnt < serv_resp.s_aliases_cnt; })) + { + /* We cannot use the database. */ + if (mapped->head->gc_cycle != gc_cycle) + retval = -2; + goto out_close; + } + + retval = 0; + *result = resultbuf; + } + } + else + { + /* Set errno to 0 to indicate no error, just no found record. */ + __set_errno (0); + /* Even though we have not found anything, the result is zero. */ + retval = 0; + } + + out_close: + if (sock != -1) + close_not_cancel_no_status (sock); + out: + if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0) + { + /* When we come here this means there has been a GC cycle while we + were looking for the data. This means the data might have been + inconsistent. Retry if possible. */ + if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1) + { + /* nscd is just running gc now. Disable using the mapping. */ + if (atomic_decrement_val (&mapped->counter) == 0) + __nscd_unmap (mapped); + mapped = NO_MAPPING; + } + + if (retval != -1) + { + if (!alloca_aliases_len) + free ((void *) aliases_len); + goto retry; + } + } + + if (!alloca_aliases_len) + free ((void *) aliases_len); + if (!alloca_key) + free (key); + + return retval; +} diff --git a/REORG.TODO/nscd/nscd_helper.c b/REORG.TODO/nscd/nscd_helper.c new file mode 100644 index 0000000000..22905d0b83 --- /dev/null +++ b/REORG.TODO/nscd/nscd_helper.c @@ -0,0 +1,564 @@ +/* Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <not-cancel.h> +#include <nis/rpcsvc/nis.h> +#include <kernel-features.h> + +#include "nscd-client.h" + + +/* Extra time we wait if the socket is still receiving data. This + value is in milliseconds. Note that the other side is nscd on the + local machine and it is already transmitting data. So the wait + time need not be long. */ +#define EXTRA_RECEIVE_TIME 200 + + +static int +wait_on_socket (int sock, long int usectmo) +{ + struct pollfd fds[1]; + fds[0].fd = sock; + fds[0].events = POLLIN | POLLERR | POLLHUP; + int n = __poll (fds, 1, usectmo); + if (n == -1 && __builtin_expect (errno == EINTR, 0)) + { + /* Handle the case where the poll() call is interrupted by a + signal. We cannot just use TEMP_FAILURE_RETRY since it might + lead to infinite loops. */ + struct timeval now; + (void) __gettimeofday (&now, NULL); + long int end = now.tv_sec * 1000 + usectmo + (now.tv_usec + 500) / 1000; + long int timeout = usectmo; + while (1) + { + n = __poll (fds, 1, timeout); + if (n != -1 || errno != EINTR) + break; + + /* Recompute the timeout time. */ + (void) __gettimeofday (&now, NULL); + timeout = end - (now.tv_sec * 1000 + (now.tv_usec + 500) / 1000); + } + } + + return n; +} + + +ssize_t +__readall (int fd, void *buf, size_t len) +{ + size_t n = len; + ssize_t ret; + do + { + again: + ret = TEMP_FAILURE_RETRY (__read (fd, buf, n)); + if (ret <= 0) + { + if (__builtin_expect (ret < 0 && errno == EAGAIN, 0) + /* The socket is still receiving data. Wait a bit more. */ + && wait_on_socket (fd, EXTRA_RECEIVE_TIME) > 0) + goto again; + + break; + } + buf = (char *) buf + ret; + n -= ret; + } + while (n > 0); + return ret < 0 ? ret : len - n; +} + + +ssize_t +__readvall (int fd, const struct iovec *iov, int iovcnt) +{ + ssize_t ret = TEMP_FAILURE_RETRY (__readv (fd, iov, iovcnt)); + if (ret <= 0) + { + if (__glibc_likely (ret == 0 || errno != EAGAIN)) + /* A genuine error or no data to read. */ + return ret; + + /* The data has not all yet been received. Do as if we have not + read anything yet. */ + ret = 0; + } + + size_t total = 0; + for (int i = 0; i < iovcnt; ++i) + total += iov[i].iov_len; + + if (ret < total) + { + struct iovec iov_buf[iovcnt]; + ssize_t r = ret; + + struct iovec *iovp = memcpy (iov_buf, iov, iovcnt * sizeof (*iov)); + do + { + while (iovp->iov_len <= r) + { + r -= iovp->iov_len; + --iovcnt; + ++iovp; + } + iovp->iov_base = (char *) iovp->iov_base + r; + iovp->iov_len -= r; + again: + r = TEMP_FAILURE_RETRY (__readv (fd, iovp, iovcnt)); + if (r <= 0) + { + if (__builtin_expect (r < 0 && errno == EAGAIN, 0) + /* The socket is still receiving data. Wait a bit more. */ + && wait_on_socket (fd, EXTRA_RECEIVE_TIME) > 0) + goto again; + + break; + } + ret += r; + } + while (ret < total); + if (r < 0) + ret = r; + } + return ret; +} + + +static int +open_socket (request_type type, const char *key, size_t keylen) +{ + int sock; + + sock = __socket (PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sock < 0) + return -1; + + size_t real_sizeof_reqdata = sizeof (request_header) + keylen; + struct + { + request_header req; + char key[]; + } *reqdata = alloca (real_sizeof_reqdata); + + struct sockaddr_un sun; + sun.sun_family = AF_UNIX; + strcpy (sun.sun_path, _PATH_NSCDSOCKET); + if (__connect (sock, (struct sockaddr *) &sun, sizeof (sun)) < 0 + && errno != EINPROGRESS) + goto out; + + reqdata->req.version = NSCD_VERSION; + reqdata->req.type = type; + reqdata->req.key_len = keylen; + + memcpy (reqdata->key, key, keylen); + + bool first_try = true; + struct timeval tvend; + /* Fake initializing tvend. */ + asm ("" : "=m" (tvend)); + while (1) + { +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + ssize_t wres = TEMP_FAILURE_RETRY (__send (sock, reqdata, + real_sizeof_reqdata, + MSG_NOSIGNAL)); + if (__glibc_likely (wres == (ssize_t) real_sizeof_reqdata)) + /* We managed to send the request. */ + return sock; + + if (wres != -1 || errno != EAGAIN) + /* Something is really wrong, no chance to continue. */ + break; + + /* The daemon is busy wait for it. */ + int to; + struct timeval now; + (void) __gettimeofday (&now, NULL); + if (first_try) + { + tvend.tv_usec = now.tv_usec; + tvend.tv_sec = now.tv_sec + 5; + to = 5 * 1000; + first_try = false; + } + else + to = ((tvend.tv_sec - now.tv_sec) * 1000 + + (tvend.tv_usec - now.tv_usec) / 1000); + + struct pollfd fds[1]; + fds[0].fd = sock; + fds[0].events = POLLOUT | POLLERR | POLLHUP; + if (__poll (fds, 1, to) <= 0) + /* The connection timed out or broke down. */ + break; + + /* We try to write again. */ + } + + out: + close_not_cancel_no_status (sock); + + return -1; +} + + +void +__nscd_unmap (struct mapped_database *mapped) +{ + assert (mapped->counter == 0); + __munmap ((void *) mapped->head, mapped->mapsize); + free (mapped); +} + + +/* Try to get a file descriptor for the shared meory segment + containing the database. */ +struct mapped_database * +__nscd_get_mapping (request_type type, const char *key, + struct mapped_database **mappedp) +{ + struct mapped_database *result = NO_MAPPING; +#ifdef SCM_RIGHTS + const size_t keylen = strlen (key) + 1; + int saved_errno = errno; + + int mapfd = -1; + char resdata[keylen]; + + /* Open a socket and send the request. */ + int sock = open_socket (type, key, keylen); + if (sock < 0) + goto out; + + /* Room for the data sent along with the file descriptor. We expect + the key name back. */ + uint64_t mapsize; + struct iovec iov[2]; + iov[0].iov_base = resdata; + iov[0].iov_len = keylen; + iov[1].iov_base = &mapsize; + iov[1].iov_len = sizeof (mapsize); + + union + { + struct cmsghdr hdr; + char bytes[CMSG_SPACE (sizeof (int))]; + } buf; + struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 2, + .msg_control = buf.bytes, + .msg_controllen = sizeof (buf) }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR (&msg); + + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN (sizeof (int)); + + /* This access is well-aligned since BUF is correctly aligned for an + int and CMSG_DATA preserves this alignment. */ + memset (CMSG_DATA (cmsg), '\xff', sizeof (int)); + + msg.msg_controllen = cmsg->cmsg_len; + + if (wait_on_socket (sock, 5 * 1000) <= 0) + goto out_close2; + +# ifndef MSG_CMSG_CLOEXEC +# define MSG_CMSG_CLOEXEC 0 +# endif + ssize_t n = TEMP_FAILURE_RETRY (__recvmsg (sock, &msg, MSG_CMSG_CLOEXEC)); + + if (__builtin_expect (CMSG_FIRSTHDR (&msg) == NULL + || (CMSG_FIRSTHDR (&msg)->cmsg_len + != CMSG_LEN (sizeof (int))), 0)) + goto out_close2; + + int *ip = (void *) CMSG_DATA (cmsg); + mapfd = *ip; + + if (__glibc_unlikely (n != keylen && n != keylen + sizeof (mapsize))) + goto out_close; + + if (__glibc_unlikely (strcmp (resdata, key) != 0)) + goto out_close; + + if (__glibc_unlikely (n == keylen)) + { + struct stat64 st; + if (__builtin_expect (fstat64 (mapfd, &st) != 0, 0) + || __builtin_expect (st.st_size < sizeof (struct database_pers_head), + 0)) + goto out_close; + + mapsize = st.st_size; + } + + /* The file is large enough, map it now. */ + void *mapping = __mmap (NULL, mapsize, PROT_READ, MAP_SHARED, mapfd, 0); + if (__glibc_likely (mapping != MAP_FAILED)) + { + /* Check whether the database is correct and up-to-date. */ + struct database_pers_head *head = mapping; + + if (__builtin_expect (head->version != DB_VERSION, 0) + || __builtin_expect (head->header_size != sizeof (*head), 0) + /* Catch some misconfiguration. The server should catch + them now but some older versions did not. */ + || __builtin_expect (head->module == 0, 0) + /* This really should not happen but who knows, maybe the update + thread got stuck. */ + || __builtin_expect (! head->nscd_certainly_running + && (head->timestamp + MAPPING_TIMEOUT + < time (NULL)), 0)) + { + out_unmap: + __munmap (mapping, mapsize); + goto out_close; + } + + size_t size = (sizeof (*head) + roundup (head->module * sizeof (ref_t), + ALIGN) + + head->data_size); + + if (__glibc_unlikely (mapsize < size)) + goto out_unmap; + + /* Allocate a record for the mapping. */ + struct mapped_database *newp = malloc (sizeof (*newp)); + if (newp == NULL) + /* Ugh, after all we went through the memory allocation failed. */ + goto out_unmap; + + newp->head = mapping; + newp->data = ((char *) mapping + head->header_size + + roundup (head->module * sizeof (ref_t), ALIGN)); + newp->mapsize = size; + newp->datasize = head->data_size; + /* Set counter to 1 to show it is usable. */ + newp->counter = 1; + + result = newp; + } + + out_close: + __close (mapfd); + out_close2: + __close (sock); + out: + __set_errno (saved_errno); +#endif /* SCM_RIGHTS */ + + struct mapped_database *oldval = *mappedp; + *mappedp = result; + + if (oldval != NULL && atomic_decrement_val (&oldval->counter) == 0) + __nscd_unmap (oldval); + + return result; +} + +struct mapped_database * +__nscd_get_map_ref (request_type type, const char *name, + volatile struct locked_map_ptr *mapptr, int *gc_cyclep) +{ + struct mapped_database *cur = mapptr->mapped; + if (cur == NO_MAPPING) + return cur; + + if (!__nscd_acquire_maplock (mapptr)) + return NO_MAPPING; + + cur = mapptr->mapped; + + if (__glibc_likely (cur != NO_MAPPING)) + { + /* If not mapped or timestamp not updated, request new map. */ + if (cur == NULL + || (cur->head->nscd_certainly_running == 0 + && cur->head->timestamp + MAPPING_TIMEOUT < time (NULL)) + || cur->head->data_size > cur->datasize) + cur = __nscd_get_mapping (type, name, + (struct mapped_database **) &mapptr->mapped); + + if (__glibc_likely (cur != NO_MAPPING)) + { + if (__builtin_expect (((*gc_cyclep = cur->head->gc_cycle) & 1) != 0, + 0)) + cur = NO_MAPPING; + else + atomic_increment (&cur->counter); + } + } + + mapptr->lock = 0; + + return cur; +} + + +/* Using sizeof (hashentry) is not always correct to determine the size of + the data structure as found in the nscd cache. The program could be + a 64-bit process and nscd could be a 32-bit process. In this case + sizeof (hashentry) would overestimate the size. The following is + the minimum size of such an entry, good enough for our tests here. */ +#define MINIMUM_HASHENTRY_SIZE \ + (offsetof (struct hashentry, dellist) + sizeof (int32_t)) + + +/* Don't return const struct datahead *, as eventhough the record + is normally constant, it can change arbitrarily during nscd + garbage collection. */ +struct datahead * +__nscd_cache_search (request_type type, const char *key, size_t keylen, + const struct mapped_database *mapped, size_t datalen) +{ + unsigned long int hash = __nis_hash (key, keylen) % mapped->head->module; + size_t datasize = mapped->datasize; + + ref_t trail = mapped->head->array[hash]; + trail = atomic_forced_read (trail); + ref_t work = trail; + size_t loop_cnt = datasize / (MINIMUM_HASHENTRY_SIZE + + offsetof (struct datahead, data) / 2); + int tick = 0; + + while (work != ENDREF && work + MINIMUM_HASHENTRY_SIZE <= datasize) + { + struct hashentry *here = (struct hashentry *) (mapped->data + work); + ref_t here_key, here_packet; + +#if !_STRING_ARCH_unaligned + /* Although during garbage collection when moving struct hashentry + records around we first copy from old to new location and then + adjust pointer from previous hashentry to it, there is no barrier + between those memory writes. It is very unlikely to hit it, + so check alignment only if a misaligned load can crash the + application. */ + if ((uintptr_t) here & (__alignof__ (*here) - 1)) + return NULL; +#endif + + if (type == here->type + && keylen == here->len + && (here_key = atomic_forced_read (here->key)) + keylen <= datasize + && memcmp (key, mapped->data + here_key, keylen) == 0 + && ((here_packet = atomic_forced_read (here->packet)) + + sizeof (struct datahead) <= datasize)) + { + /* We found the entry. Increment the appropriate counter. */ + struct datahead *dh + = (struct datahead *) (mapped->data + here_packet); + +#if !_STRING_ARCH_unaligned + if ((uintptr_t) dh & (__alignof__ (*dh) - 1)) + return NULL; +#endif + + /* See whether we must ignore the entry or whether something + is wrong because garbage collection is in progress. */ + if (dh->usable + && here_packet + dh->allocsize <= datasize + && (here_packet + offsetof (struct datahead, data) + datalen + <= datasize)) + return dh; + } + + work = atomic_forced_read (here->next); + /* Prevent endless loops. This should never happen but perhaps + the database got corrupted, accidentally or deliberately. */ + if (work == trail || loop_cnt-- == 0) + break; + if (tick) + { + struct hashentry *trailelem; + trailelem = (struct hashentry *) (mapped->data + trail); + +#if !_STRING_ARCH_unaligned + /* We have to redo the checks. Maybe the data changed. */ + if ((uintptr_t) trailelem & (__alignof__ (*trailelem) - 1)) + return NULL; +#endif + + if (trail + MINIMUM_HASHENTRY_SIZE > datasize) + return NULL; + + trail = atomic_forced_read (trailelem->next); + } + tick = 1 - tick; + } + + return NULL; +} + + +/* Create a socket connected to a name. */ +int +__nscd_open_socket (const char *key, size_t keylen, request_type type, + void *response, size_t responselen) +{ + /* This should never happen and it is something the nscd daemon + enforces, too. He it helps to limit the amount of stack + used. */ + if (keylen > MAXKEYLEN) + return -1; + + int saved_errno = errno; + + int sock = open_socket (type, key, keylen); + if (sock >= 0) + { + /* Wait for data. */ + if (wait_on_socket (sock, 5 * 1000) > 0) + { + ssize_t nbytes = TEMP_FAILURE_RETRY (__read (sock, response, + responselen)); + if (nbytes == (ssize_t) responselen) + return sock; + } + + close_not_cancel_no_status (sock); + } + + __set_errno (saved_errno); + + return -1; +} diff --git a/REORG.TODO/nscd/nscd_initgroups.c b/REORG.TODO/nscd/nscd_initgroups.c new file mode 100644 index 0000000000..00c650896a --- /dev/null +++ b/REORG.TODO/nscd/nscd_initgroups.c @@ -0,0 +1,180 @@ +/* Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2004. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <assert.h> +#include <errno.h> +#include <grp.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <not-cancel.h> + +#include "nscd-client.h" +#include "nscd_proto.h" + + +/* We use the same mapping as in nscd_getgr. */ +libc_locked_map_ptr (extern, __gr_map_handle) attribute_hidden; + + +int +__nscd_getgrouplist (const char *user, gid_t group, long int *size, + gid_t **groupsp, long int limit) +{ + size_t userlen = strlen (user) + 1; + int gc_cycle; + int nretries = 0; + + /* If the mapping is available, try to search there instead of + communicating with the nscd. */ + struct mapped_database *mapped; + mapped = __nscd_get_map_ref (GETFDGR, "group", &__gr_map_handle, &gc_cycle); + + retry:; + char *respdata = NULL; + int retval = -1; + int sock = -1; + initgr_response_header initgr_resp; + + if (mapped != NO_MAPPING) + { + struct datahead *found = __nscd_cache_search (INITGROUPS, user, + userlen, mapped, + sizeof initgr_resp); + if (found != NULL) + { + respdata = (char *) (&found->data[0].initgrdata + 1); + initgr_resp = found->data[0].initgrdata; + char *recend = (char *) found->data + found->recsize; + + /* Now check if we can trust initgr_resp fields. If GC is + in progress, it can contain anything. */ + if (mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out; + } + + if (respdata + initgr_resp.ngrps * sizeof (int32_t) > recend) + goto out; + } + } + + /* If we do not have the cache mapped, try to get the data over the + socket. */ + if (respdata == NULL) + { + sock = __nscd_open_socket (user, userlen, INITGROUPS, &initgr_resp, + sizeof (initgr_resp)); + if (sock == -1) + { + /* nscd not running or wrong version. */ + __nss_not_use_nscd_group = 1; + goto out; + } + } + + if (initgr_resp.found == 1) + { + /* The following code assumes that gid_t and int32_t are the + same size. This is the case for al existing implementation. + If this should change some code needs to be added which + doesn't use memcpy but instead copies each array element one + by one. */ + assert (sizeof (int32_t) == sizeof (gid_t)); + assert (initgr_resp.ngrps >= 0); + + /* Make sure we have enough room. We always count GROUP in even + though we might not end up adding it. */ + if (*size < initgr_resp.ngrps + 1) + { + gid_t *newp = realloc (*groupsp, + (initgr_resp.ngrps + 1) * sizeof (gid_t)); + if (newp == NULL) + /* We cannot increase the buffer size. */ + goto out_close; + + *groupsp = newp; + *size = initgr_resp.ngrps + 1; + } + + if (respdata == NULL) + { + /* Read the data from the socket. */ + if ((size_t) __readall (sock, *groupsp, initgr_resp.ngrps + * sizeof (gid_t)) + == initgr_resp.ngrps * sizeof (gid_t)) + retval = initgr_resp.ngrps; + } + else + { + /* Just copy the data. */ + retval = initgr_resp.ngrps; + memcpy (*groupsp, respdata, retval * sizeof (gid_t)); + } + } + else + { + if (__glibc_unlikely (initgr_resp.found == -1)) + { + /* The daemon does not cache this database. */ + __nss_not_use_nscd_group = 1; + goto out_close; + } + + /* No group found yet. */ + retval = 0; + + assert (*size >= 1); + } + + /* Check whether GROUP is part of the mix. If not, add it. */ + if (retval >= 0) + { + int cnt; + for (cnt = 0; cnt < retval; ++cnt) + if ((*groupsp)[cnt] == group) + break; + + if (cnt == retval) + (*groupsp)[retval++] = group; + } + + out_close: + if (sock != -1) + close_not_cancel_no_status (sock); + out: + if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0) + { + /* When we come here this means there has been a GC cycle while we + were looking for the data. This means the data might have been + inconsistent. Retry if possible. */ + if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1) + { + /* nscd is just running gc now. Disable using the mapping. */ + if (atomic_decrement_val (&mapped->counter) == 0) + __nscd_unmap (mapped); + mapped = NO_MAPPING; + } + + if (retval != -1) + goto retry; + } + + return retval; +} diff --git a/REORG.TODO/nscd/nscd_netgroup.c b/REORG.TODO/nscd/nscd_netgroup.c new file mode 100644 index 0000000000..44f37ef957 --- /dev/null +++ b/REORG.TODO/nscd/nscd_netgroup.c @@ -0,0 +1,289 @@ +/* Copyright (C) 2011-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@gmail.com>, 2011. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <not-cancel.h> + +#include "nscd-client.h" +#include "nscd_proto.h" + +int __nss_not_use_nscd_netgroup; + + +libc_locked_map_ptr (static, map_handle); +/* Note that we only free the structure if necessary. The memory + mapping is not removed since it is not visible to the malloc + handling. */ +libc_freeres_fn (pw_map_free) +{ + if (map_handle.mapped != NO_MAPPING) + { + void *p = map_handle.mapped; + map_handle.mapped = NO_MAPPING; + free (p); + } +} + + +int +__nscd_setnetgrent (const char *group, struct __netgrent *datap) +{ + int gc_cycle; + int nretries = 0; + size_t group_len = strlen (group) + 1; + + /* If the mapping is available, try to search there instead of + communicating with the nscd. */ + struct mapped_database *mapped; + mapped = __nscd_get_map_ref (GETFDNETGR, "netgroup", &map_handle, &gc_cycle); + + retry:; + char *respdata = NULL; + int retval = -1; + netgroup_response_header netgroup_resp; + + if (mapped != NO_MAPPING) + { + struct datahead *found = __nscd_cache_search (GETNETGRENT, group, + group_len, mapped, + sizeof netgroup_resp); + if (found != NULL) + { + respdata = (char *) (&found->data[0].netgroupdata + 1); + netgroup_resp = found->data[0].netgroupdata; + /* Now check if we can trust pw_resp fields. If GC is + in progress, it can contain anything. */ + if (mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out; + } + } + } + + int sock = -1; + if (respdata == NULL) + { + sock = __nscd_open_socket (group, group_len, GETNETGRENT, + &netgroup_resp, sizeof (netgroup_resp)); + if (sock == -1) + { + /* nscd not running or wrong version. */ + __nss_not_use_nscd_netgroup = 1; + goto out; + } + } + + if (netgroup_resp.found == 1) + { + size_t datalen = netgroup_resp.result_len; + + /* If we do not have to read the data here it comes from the + mapped data and does not have to be freed. */ + if (respdata == NULL) + { + /* The data will come via the socket. */ + respdata = malloc (datalen); + if (respdata == NULL) + goto out_close; + + if ((size_t) __readall (sock, respdata, datalen) != datalen) + { + free (respdata); + goto out_close; + } + } + + datap->data = respdata; + datap->data_size = datalen; + datap->cursor = respdata; + datap->first = 1; + datap->nip = (service_user *) -1l; + datap->known_groups = NULL; + datap->needed_groups = NULL; + + retval = 1; + } + else + { + if (__glibc_unlikely (netgroup_resp.found == -1)) + { + /* The daemon does not cache this database. */ + __nss_not_use_nscd_netgroup = 1; + goto out_close; + } + + /* Set errno to 0 to indicate no error, just no found record. */ + __set_errno (0); + /* Even though we have not found anything, the result is zero. */ + retval = 0; + } + + out_close: + if (sock != -1) + close_not_cancel_no_status (sock); + out: + if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0) + { + /* When we come here this means there has been a GC cycle while we + were looking for the data. This means the data might have been + inconsistent. Retry if possible. */ + if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1) + { + /* nscd is just running gc now. Disable using the mapping. */ + if (atomic_decrement_val (&mapped->counter) == 0) + __nscd_unmap (mapped); + mapped = NO_MAPPING; + } + + if (retval != -1) + goto retry; + } + + return retval; +} + + +int +__nscd_innetgr (const char *netgroup, const char *host, const char *user, + const char *domain) +{ + size_t key_len = (strlen (netgroup) + strlen (host ?: "") + + strlen (user ?: "") + strlen (domain ?: "") + 7); + char *key; + bool use_alloca = __libc_use_alloca (key_len); + if (use_alloca) + key = alloca (key_len); + else + { + key = malloc (key_len); + if (key == NULL) + return -1; + } + char *wp = stpcpy (key, netgroup) + 1; + if (host != NULL) + { + *wp++ = '\1'; + wp = stpcpy (wp, host) + 1; + } + else + *wp++ = '\0'; + if (user != NULL) + { + *wp++ = '\1'; + wp = stpcpy (wp, user) + 1; + } + else + *wp++ = '\0'; + if (domain != NULL) + { + *wp++ = '\1'; + wp = stpcpy (wp, domain) + 1; + } + else + *wp++ = '\0'; + key_len = wp - key; + + /* If the mapping is available, try to search there instead of + communicating with the nscd. */ + int gc_cycle; + int nretries = 0; + struct mapped_database *mapped; + mapped = __nscd_get_map_ref (GETFDNETGR, "netgroup", &map_handle, &gc_cycle); + + retry:; + int retval = -1; + innetgroup_response_header innetgroup_resp; + int sock = -1; + + if (mapped != NO_MAPPING) + { + struct datahead *found = __nscd_cache_search (INNETGR, key, + key_len, mapped, + sizeof innetgroup_resp); + if (found != NULL) + { + innetgroup_resp = found->data[0].innetgroupdata; + /* Now check if we can trust pw_resp fields. If GC is + in progress, it can contain anything. */ + if (mapped->head->gc_cycle != gc_cycle) + { + retval = -2; + goto out; + } + + goto found_entry; + } + } + + sock = __nscd_open_socket (key, key_len, INNETGR, + &innetgroup_resp, sizeof (innetgroup_resp)); + if (sock == -1) + { + /* nscd not running or wrong version. */ + __nss_not_use_nscd_netgroup = 1; + goto out; + } + + found_entry: + if (innetgroup_resp.found == 1) + retval = innetgroup_resp.result; + else + { + if (__glibc_unlikely (innetgroup_resp.found == -1)) + { + /* The daemon does not cache this database. */ + __nss_not_use_nscd_netgroup = 1; + goto out_close; + } + + /* Set errno to 0 to indicate no error, just no found record. */ + __set_errno (0); + /* Even though we have not found anything, the result is zero. */ + retval = 0; + } + + out_close: + if (sock != -1) + close_not_cancel_no_status (sock); + out: + if (__nscd_drop_map_ref (mapped, &gc_cycle) != 0) + { + /* When we come here this means there has been a GC cycle while we + were looking for the data. This means the data might have been + inconsistent. Retry if possible. */ + if ((gc_cycle & 1) != 0 || ++nretries == 5 || retval == -1) + { + /* nscd is just running gc now. Disable using the mapping. */ + if (atomic_decrement_val (&mapped->counter) == 0) + __nscd_unmap (mapped); + mapped = NO_MAPPING; + } + + if (retval != -1) + goto retry; + } + + if (! use_alloca) + free (key); + + return retval; +} diff --git a/REORG.TODO/nscd/nscd_proto.h b/REORG.TODO/nscd/nscd_proto.h new file mode 100644 index 0000000000..7c61821e74 --- /dev/null +++ b/REORG.TODO/nscd/nscd_proto.h @@ -0,0 +1,79 @@ +/* Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@suse.de>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#ifndef _NSCD_PROTO_H +#define _NSCD_PROTO_H 1 + +#include <grp.h> +#include <netdb.h> +#include <pwd.h> + +/* Interval in which we transfer retry to contact the NSCD. */ +#define NSS_NSCD_RETRY 100 + +/* Type needed in the interfaces. */ +struct nscd_ai_result; + + +/* Variables for communication between NSCD handler functions and NSS. */ +extern int __nss_not_use_nscd_passwd attribute_hidden; +extern int __nss_not_use_nscd_group attribute_hidden; +extern int __nss_not_use_nscd_hosts attribute_hidden; +extern int __nss_not_use_nscd_services attribute_hidden; +extern int __nss_not_use_nscd_netgroup attribute_hidden; + +extern int __nscd_getpwnam_r (const char *name, struct passwd *resultbuf, + char *buffer, size_t buflen, + struct passwd **result); +extern int __nscd_getpwuid_r (uid_t uid, struct passwd *resultbuf, + char *buffer, size_t buflen, + struct passwd **result); +extern int __nscd_getgrnam_r (const char *name, struct group *resultbuf, + char *buffer, size_t buflen, + struct group **result); +extern int __nscd_getgrgid_r (gid_t gid, struct group *resultbuf, + char *buffer, size_t buflen, + struct group **result); +extern int __nscd_gethostbyname_r (const char *name, + struct hostent *resultbuf, + char *buffer, size_t buflen, + struct hostent **result, int *h_errnop); +extern int __nscd_gethostbyname2_r (const char *name, int af, + struct hostent *resultbuf, + char *buffer, size_t buflen, + struct hostent **result, int *h_errnop); +extern int __nscd_gethostbyaddr_r (const void *addr, socklen_t len, int type, + struct hostent *resultbuf, + char *buffer, size_t buflen, + struct hostent **result, int *h_errnop); +extern int __nscd_getai (const char *key, struct nscd_ai_result **result, + int *h_errnop); +extern int __nscd_getgrouplist (const char *user, gid_t group, long int *size, + gid_t **groupsp, long int limit); +extern int __nscd_getservbyname_r (const char *name, const char *proto, + struct servent *result_buf, char *buf, + size_t buflen, struct servent **result); +extern int __nscd_getservbyport_r (int port, const char *proto, + struct servent *result_buf, char *buf, + size_t buflen, struct servent **result); +extern int __nscd_innetgr (const char *netgroup, const char *host, + const char *user, const char *domain); +extern int __nscd_setnetgrent (const char *group, struct __netgrent *datap); + + +#endif /* _NSCD_PROTO_H */ diff --git a/REORG.TODO/nscd/nscd_setup_thread.c b/REORG.TODO/nscd/nscd_setup_thread.c new file mode 100644 index 0000000000..c3670ad943 --- /dev/null +++ b/REORG.TODO/nscd/nscd_setup_thread.c @@ -0,0 +1,27 @@ +/* Setup of nscd worker threads. Stub verison. + Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@redhat.com>, 2004. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <nscd.h> + + +int +setup_thread (struct database_dyn *db) +{ + /* Nothing. */ + return 0; +} diff --git a/REORG.TODO/nscd/nscd_stat.c b/REORG.TODO/nscd/nscd_stat.c new file mode 100644 index 0000000000..feb1c98ac3 --- /dev/null +++ b/REORG.TODO/nscd/nscd_stat.c @@ -0,0 +1,318 @@ +/* Copyright (c) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Thorsten Kukuk <kukuk@vt.uni-paderborn.de>, 1998. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <errno.h> +#include <error.h> +#include <inttypes.h> +#include <langinfo.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <unistd.h> +#include <libintl.h> + +#include "nscd.h" +#include "dbg_log.h" +#include "selinux.h" +#ifdef HAVE_SELINUX +# include <selinux/selinux.h> +# include <selinux/avc.h> +#endif /* HAVE_SELINUX */ + + +/* We use this to make sure the receiver is the same. */ +static const char compilation[21] = __DATE__ " " __TIME__; + +/* Statistic data for one database. */ +struct dbstat +{ + int enabled; + int check_file; + int shared; + int persistent; + size_t module; + + unsigned long int postimeout; + unsigned long int negtimeout; + + size_t nentries; + size_t maxnentries; + size_t maxnsearched; + size_t datasize; + size_t dataused; + + uintmax_t poshit; + uintmax_t neghit; + uintmax_t posmiss; + uintmax_t negmiss; + + uintmax_t rdlockdelayed; + uintmax_t wrlockdelayed; + + uintmax_t addfailed; +}; + +/* Record for transmitting statistics. */ +struct statdata +{ + char version[sizeof (compilation)]; + int debug_level; + time_t runtime; + unsigned long int client_queued; + int nthreads; + int max_nthreads; + int paranoia; + time_t restart_interval; + unsigned int reload_count; + int ndbs; + struct dbstat dbs[lastdb]; +#ifdef HAVE_SELINUX + struct avc_cache_stats cstats; +#endif /* HAVE_SELINUX */ +}; + + +void +send_stats (int fd, struct database_dyn dbs[lastdb]) +{ + struct statdata data; + int cnt; + + memset (&data, 0, sizeof (data)); + + memcpy (data.version, compilation, sizeof (compilation)); + data.debug_level = debug_level; + data.runtime = time (NULL) - start_time; + data.client_queued = client_queued; + data.nthreads = nthreads; + data.max_nthreads = max_nthreads; + data.paranoia = paranoia; + data.restart_interval = restart_interval; + data.reload_count = reload_count; + data.ndbs = lastdb; + + for (cnt = 0; cnt < lastdb; ++cnt) + { + memset (&data.dbs[cnt], 0, sizeof (data.dbs[cnt])); + data.dbs[cnt].enabled = dbs[cnt].enabled; + data.dbs[cnt].check_file = dbs[cnt].check_file; + data.dbs[cnt].shared = dbs[cnt].shared; + data.dbs[cnt].persistent = dbs[cnt].persistent; + data.dbs[cnt].postimeout = dbs[cnt].postimeout; + data.dbs[cnt].negtimeout = dbs[cnt].negtimeout; + if (dbs[cnt].head != NULL) + { + data.dbs[cnt].module = dbs[cnt].head->module; + data.dbs[cnt].poshit = dbs[cnt].head->poshit; + data.dbs[cnt].neghit = dbs[cnt].head->neghit; + data.dbs[cnt].posmiss = dbs[cnt].head->posmiss; + data.dbs[cnt].negmiss = dbs[cnt].head->negmiss; + data.dbs[cnt].nentries = dbs[cnt].head->nentries; + data.dbs[cnt].maxnentries = dbs[cnt].head->maxnentries; + data.dbs[cnt].datasize = dbs[cnt].head->data_size; + data.dbs[cnt].dataused = dbs[cnt].head->first_free; + data.dbs[cnt].maxnsearched = dbs[cnt].head->maxnsearched; + data.dbs[cnt].rdlockdelayed = dbs[cnt].head->rdlockdelayed; + data.dbs[cnt].wrlockdelayed = dbs[cnt].head->wrlockdelayed; + data.dbs[cnt].addfailed = dbs[cnt].head->addfailed; + } + } + + if (selinux_enabled) + nscd_avc_cache_stats (&data.cstats); + + if (TEMP_FAILURE_RETRY (send (fd, &data, sizeof (data), MSG_NOSIGNAL)) + != sizeof (data)) + { + char buf[256]; + dbg_log (_("cannot write statistics: %s"), + strerror_r (errno, buf, sizeof (buf))); + } +} + + +int +receive_print_stats (void) +{ + struct statdata data; + request_header req; + ssize_t nbytes; + int fd; + int i; + uid_t uid = getuid (); + const char *yesstr = _("yes"); + const char *nostr = _("no"); + + /* Find out whether there is another user but root allowed to + request statistics. */ + if (uid != 0) + { + /* User specified? */ + if(stat_user == NULL || stat_uid != uid) + { + if (stat_user != NULL) + error (EXIT_FAILURE, 0, + _("Only root or %s is allowed to use this option!"), + stat_user); + else + error (EXIT_FAILURE, 0, + _("Only root is allowed to use this option!")); + } + } + + /* Open a socket to the running nscd. */ + fd = nscd_open_socket (); + if (fd == -1) + error (EXIT_FAILURE, 0, _("nscd not running!\n")); + + /* Send the request. */ + req.version = NSCD_VERSION; + req.type = GETSTAT; + req.key_len = 0; + nbytes = TEMP_FAILURE_RETRY (send (fd, &req, sizeof (request_header), + MSG_NOSIGNAL)); + if (nbytes != sizeof (request_header)) + { + int err = errno; + close (fd); + error (EXIT_FAILURE, err, _("write incomplete")); + } + + /* Read as much data as we expect. */ + if (TEMP_FAILURE_RETRY (read (fd, &data, sizeof (data))) != sizeof (data) + || (memcmp (data.version, compilation, sizeof (compilation)) != 0 + /* Yes, this is an assignment! */ + && (errno = EINVAL))) + { + /* Not the right version. */ + int err = errno; + close (fd); + error (EXIT_FAILURE, err, _("cannot read statistics data")); + } + + printf (_("nscd configuration:\n\n%15d server debug level\n"), + data.debug_level); + + /* We know that we can simply subtract time_t values. */ + unsigned long int diff = data.runtime; + unsigned int ndays = 0; + unsigned int nhours = 0; + unsigned int nmins = 0; + if (diff > 24 * 60 * 60) + { + ndays = diff / (24 * 60 * 60); + diff %= 24 * 60 * 60; + } + if (diff > 60 * 60) + { + nhours = diff / (60 * 60); + diff %= 60 * 60; + } + if (diff > 60) + { + nmins = diff / 60; + diff %= 60; + } + if (ndays != 0) + printf (_("%3ud %2uh %2um %2lus server runtime\n"), + ndays, nhours, nmins, diff); + else if (nhours != 0) + printf (_(" %2uh %2um %2lus server runtime\n"), nhours, nmins, diff); + else if (nmins != 0) + printf (_(" %2um %2lus server runtime\n"), nmins, diff); + else + printf (_(" %2lus server runtime\n"), diff); + + 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" + "%15u reload count\n"), + data.nthreads, data.max_nthreads, data.client_queued, + data.paranoia ? yesstr : nostr, + (unsigned long int) data.restart_interval, data.reload_count); + + 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 = 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 ? yesstr : nostr; + if (check_file[0] == '\0') + check_file = data.dbs[i].check_file ? yesstr : nostr; + if (shared[0] == '\0') + shared = data.dbs[i].shared ? yesstr : nostr; + if (persistent[0] == '\0') + persistent = data.dbs[i].persistent ? yesstr : nostr; + + if (all == 0) + /* If nothing happened so far report a 0% hit rate. */ + all = 1; + + printf (_("\n%s cache:\n\n" + "%15s cache is enabled\n" + "%15s cache is persistent\n" + "%15s cache is shared\n" + "%15zu suggested size\n" + "%15zu total data pool size\n" + "%15zu used data pool size\n" + "%15lu seconds time to live for positive entries\n" + "%15lu seconds time to live for negative entries\n" + "%15" PRIuMAX " cache hits on positive entries\n" + "%15" PRIuMAX " cache hits on negative entries\n" + "%15" PRIuMAX " cache misses on positive entries\n" + "%15" PRIuMAX " cache misses on negative entries\n" + "%15lu%% cache hit rate\n" + "%15zu current number of cached values\n" + "%15zu maximum number of cached values\n" + "%15zu maximum chain length searched\n" + "%15" PRIuMAX " number of delays on rdlock\n" + "%15" PRIuMAX " number of delays on wrlock\n" + "%15" PRIuMAX " memory allocations failed\n" + "%15s check /etc/%s for changes\n"), + dbnames[i], enabled, persistent, shared, + data.dbs[i].module, + data.dbs[i].datasize, data.dbs[i].dataused, + data.dbs[i].postimeout, data.dbs[i].negtimeout, + data.dbs[i].poshit, data.dbs[i].neghit, + data.dbs[i].posmiss, data.dbs[i].negmiss, + (100 * hit) / all, + data.dbs[i].nentries, data.dbs[i].maxnentries, + data.dbs[i].maxnsearched, + data.dbs[i].rdlockdelayed, + data.dbs[i].wrlockdelayed, + data.dbs[i].addfailed, check_file, dbnames[i]); + } + + if (selinux_enabled) + nscd_avc_print_stats (&data.cstats); + + close (fd); + + exit (0); +} diff --git a/REORG.TODO/nscd/pwdcache.c b/REORG.TODO/nscd/pwdcache.c new file mode 100644 index 0000000000..721f4c617b --- /dev/null +++ b/REORG.TODO/nscd/pwdcache.c @@ -0,0 +1,552 @@ +/* Cache handling for passwd lookup. + Copyright (C) 1998-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1998. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> +#include <assert.h> +#include <errno.h> +#include <error.h> +#include <libintl.h> +#include <pwd.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <stackinfo.h> + +#include "nscd.h" +#include "dbg_log.h" +#ifdef HAVE_SENDFILE +# include <kernel-features.h> +#endif + +/* This is the standard reply in case the service is disabled. */ +static const pw_response_header disabled = +{ + .version = NSCD_VERSION, + .found = -1, + .pw_name_len = 0, + .pw_passwd_len = 0, + .pw_uid = -1, + .pw_gid = -1, + .pw_gecos_len = 0, + .pw_dir_len = 0, + .pw_shell_len = 0 +}; + +/* This is the struct describing how to write this record. */ +const struct iovec pwd_iov_disabled = +{ + .iov_base = (void *) &disabled, + .iov_len = sizeof (disabled) +}; + + +/* This is the standard reply in case we haven't found the dataset. */ +static const pw_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .pw_name_len = 0, + .pw_passwd_len = 0, + .pw_uid = -1, + .pw_gid = -1, + .pw_gecos_len = 0, + .pw_dir_len = 0, + .pw_shell_len = 0 +}; + + +static time_t +cache_addpw (struct database_dyn *db, int fd, request_header *req, + const void *key, struct passwd *pwd, uid_t owner, + struct hashentry *const he, struct datahead *dh, int errval) +{ + bool all_written = true; + ssize_t total; + time_t t = time (NULL); + + /* We allocate all data in one memory block: the iov vector, + the response header and the dataset itself. */ + struct dataset + { + struct datahead head; + pw_response_header resp; + char strdata[0]; + } *dataset; + + assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data)); + + time_t timeout = MAX_TIMEOUT_VALUE; + if (pwd == NULL) + { + if (he != NULL && errval == EAGAIN) + { + /* If we have an old record available but cannot find one + now because the service is not available we keep the old + record and make sure it does not get removed. */ + if (reload_count != UINT_MAX && dh->nreloads == reload_count) + /* Do not reset the value if we never not reload the record. */ + dh->nreloads = reload_count - 1; + + /* Reload with the same time-to-live value. */ + timeout = dh->timeout = t + db->postimeout; + + total = 0; + } + else + { + /* We have no data. This means we send the standard reply for this + case. */ + total = sizeof (notfound); + + if (fd != -1 + && TEMP_FAILURE_RETRY (send (fd, ¬found, total, + MSG_NOSIGNAL)) != total) + all_written = false; + + /* If we have a transient error or cannot permanently store + the result, so be it. */ + if (errno == EAGAIN || __builtin_expect (db->negtimeout == 0, 0)) + { + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + else if ((dataset = mempool_alloc (db, (sizeof (struct dataset) + + req->key_len), 1)) != NULL) + { + timeout = datahead_init_neg (&dataset->head, + (sizeof (struct dataset) + + req->key_len), total, + db->negtimeout); + + /* This is the reply. */ + memcpy (&dataset->resp, ¬found, total); + + /* Copy the key data. */ + char *key_copy = memcpy (dataset->strdata, key, req->key_len); + + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + + sizeof (struct dataset) + req->key_len, MS_ASYNC); + } + + (void) cache_add (req->type, key_copy, req->key_len, + &dataset->head, true, db, owner, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + } + } + else + { + /* Determine the I/O structure. */ + size_t pw_name_len = strlen (pwd->pw_name) + 1; + size_t pw_passwd_len = strlen (pwd->pw_passwd) + 1; + size_t pw_gecos_len = strlen (pwd->pw_gecos) + 1; + size_t pw_dir_len = strlen (pwd->pw_dir) + 1; + size_t pw_shell_len = strlen (pwd->pw_shell) + 1; + char *cp; + const size_t key_len = strlen (key); + const size_t buf_len = 3 * sizeof (pwd->pw_uid) + key_len + 1; + char *buf = alloca (buf_len); + ssize_t n; + + /* We need this to insert the `byuid' entry. */ + int key_offset; + n = snprintf (buf, buf_len, "%d%c%n%s", pwd->pw_uid, '\0', + &key_offset, (char *) key) + 1; + + total = (offsetof (struct dataset, strdata) + + pw_name_len + pw_passwd_len + + pw_gecos_len + pw_dir_len + pw_shell_len); + + /* If we refill the cache, first assume the reconrd did not + change. Allocate memory on the cache since it is likely + discarded anyway. If it turns out to be necessary to have a + new record we can still allocate real memory. */ + bool alloca_used = false; + dataset = NULL; + + if (he == NULL) + { + /* Prevent an INVALIDATE request from pruning the data between + the two calls to cache_add. */ + if (db->propagate) + pthread_mutex_lock (&db->prune_run_lock); + dataset = (struct dataset *) mempool_alloc (db, total + n, 1); + } + + if (dataset == NULL) + { + if (he == NULL && db->propagate) + pthread_mutex_unlock (&db->prune_run_lock); + + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + dataset = (struct dataset *) alloca (total + n); + + /* We cannot add this record to the permanent database. */ + alloca_used = true; + } + + timeout = datahead_init_pos (&dataset->head, total + n, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + db->postimeout); + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.pw_name_len = pw_name_len; + dataset->resp.pw_passwd_len = pw_passwd_len; + dataset->resp.pw_uid = pwd->pw_uid; + dataset->resp.pw_gid = pwd->pw_gid; + dataset->resp.pw_gecos_len = pw_gecos_len; + dataset->resp.pw_dir_len = pw_dir_len; + dataset->resp.pw_shell_len = pw_shell_len; + + cp = dataset->strdata; + + /* Copy the strings over into the buffer. */ + cp = mempcpy (cp, pwd->pw_name, pw_name_len); + cp = mempcpy (cp, pwd->pw_passwd, pw_passwd_len); + cp = mempcpy (cp, pwd->pw_gecos, pw_gecos_len); + cp = mempcpy (cp, pwd->pw_dir, pw_dir_len); + cp = mempcpy (cp, pwd->pw_shell, pw_shell_len); + + /* Finally the stringified UID value. */ + memcpy (cp, buf, n); + char *key_copy = cp + key_offset; + assert (key_copy == (char *) rawmemchr (cp, '\0') + 1); + + assert (cp == dataset->strdata + total - offsetof (struct dataset, + strdata)); + + /* Now we can determine whether on refill we have to create a new + record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (dataset->head.allocsize == dh->allocsize + && dataset->head.recsize == dh->recsize + && memcmp (&dataset->resp, dh->data, + dh->allocsize - offsetof (struct dataset, resp)) == 0) + { + /* The data has not changed. We will just bump the + timeout value. Note that the new record has been + allocated on the stack and need not be freed. */ + dh->timeout = dataset->head.timeout; + ++dh->nreloads; + } + else + { + /* We have to create a new record. Just allocate + appropriate memory and copy it. */ + struct dataset *newp + = (struct dataset *) mempool_alloc (db, total + n, 1); + if (newp != NULL) + { + /* Adjust pointer into the memory block. */ + cp = (char *) newp + (cp - (char *) dataset); + key_copy = (char *) newp + (key_copy - (char *) dataset); + + dataset = memcpy (newp, dataset, total + n); + alloca_used = false; + } + + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + else + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so would + unnecessarily let the receiver wait. */ + assert (fd != -1); + +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && !alloca_used) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + + total + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); + ssize_t written = sendfileall (fd, db->wr_fd, + (char *) &dataset->resp + - (char *) db->head, + dataset->head.recsize); + if (written != dataset->head.recsize) + { +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + all_written = false; + } + } + else +# ifndef __ASSUME_SENDFILE + use_write: +# endif +#endif + if (writeall (fd, &dataset->resp, dataset->head.recsize) + != dataset->head.recsize) + all_written = false; + } + + + /* Add the record to the database. But only if it has not been + stored on the stack. */ + if (! alloca_used) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + total + n, + MS_ASYNC); + } + + /* NB: in the following code we always must add the entry + marked with FIRST first. Otherwise we end up with + dangling "pointers" in case a latter hash entry cannot be + added. */ + bool first = true; + + /* If the request was by UID, add that entry first. */ + if (req->type == GETPWBYUID) + { + if (cache_add (GETPWBYUID, cp, key_offset, &dataset->head, true, + db, owner, he == NULL) < 0) + goto out; + + first = false; + } + /* If the key is different from the name add a separate entry. */ + else if (strcmp (key_copy, dataset->strdata) != 0) + { + if (cache_add (GETPWBYNAME, key_copy, key_len + 1, + &dataset->head, true, db, owner, he == NULL) < 0) + goto out; + + first = false; + } + + /* We have to add the value for both, byname and byuid. */ + if ((req->type == GETPWBYNAME || db->propagate) + && __builtin_expect (cache_add (GETPWBYNAME, dataset->strdata, + pw_name_len, &dataset->head, + first, db, owner, he == NULL) + == 0, 1)) + { + if (req->type == GETPWBYNAME && db->propagate) + (void) cache_add (GETPWBYUID, cp, key_offset, &dataset->head, + false, db, owner, false); + } + + out: + pthread_rwlock_unlock (&db->lock); + if (he == NULL && db->propagate) + pthread_mutex_unlock (&db->prune_run_lock); + } + } + + if (__builtin_expect (!all_written, 0) && debug_level > 0) + { + char buf[256]; + dbg_log (_("short write in %s: %s"), __FUNCTION__, + strerror_r (errno, buf, sizeof (buf))); + } + + return timeout; +} + + +union keytype +{ + void *v; + uid_t u; +}; + + +static int +lookup (int type, union keytype key, struct passwd *resultbufp, char *buffer, + size_t buflen, struct passwd **pwd) +{ + if (type == GETPWBYNAME) + return __getpwnam_r (key.v, resultbufp, buffer, buflen, pwd); + else + return __getpwuid_r (key.u, resultbufp, buffer, buflen, pwd); +} + + +static time_t +addpwbyX (struct database_dyn *db, int fd, request_header *req, + union keytype key, const char *keystr, uid_t c_uid, + struct hashentry *he, struct datahead *dh) +{ + /* Search for the entry matching the key. Please note that we don't + look again in the table whether the dataset is now available. We + simply insert it. It does not matter if it is in there twice. The + pruning function only will look at the timestamp. */ + size_t buflen = 1024; + char *buffer = (char *) alloca (buflen); + struct passwd resultbuf; + struct passwd *pwd; + bool use_malloc = false; + int errval = 0; + + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in password cache!"), keystr); + else + dbg_log (_("Reloading \"%s\" in password cache!"), keystr); + } + + while (lookup (req->type, key, &resultbuf, buffer, buflen, &pwd) != 0 + && (errval = errno) == ERANGE) + { + errno = 0; + + if (__glibc_unlikely (buflen > 32768)) + { + char *old_buffer = buffer; + buflen *= 2; + buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen); + if (buffer == NULL) + { + /* We ran out of memory. We cannot do anything but + sending a negative response. In reality this should + never happen. */ + pwd = NULL; + buffer = old_buffer; + + /* We set the error to indicate this is (possibly) a + temporary error and that it does not mean the entry + is not available at all. */ + errval = EAGAIN; + break; + } + use_malloc = true; + } + else + /* Allocate a new buffer on the stack. If possible combine it + with the previously allocated buffer. */ + buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen); + } + + /* Add the entry to the cache. */ + time_t timeout = cache_addpw (db, fd, req, keystr, pwd, c_uid, he, dh, + errval); + + if (use_malloc) + free (buffer); + + return timeout; +} + + +void +addpwbyname (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t c_uid) +{ + union keytype u = { .v = key }; + + addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL); +} + + +time_t +readdpwbyname (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETPWBYNAME, + .key_len = he->len + }; + union keytype u = { .v = db->data + he->key }; + + return addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh); +} + + +void +addpwbyuid (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t c_uid) +{ + char *ep; + uid_t uid = strtoul ((char *) key, &ep, 10); + + if (*(char *) key == '\0' || *ep != '\0') /* invalid numeric uid */ + { + if (debug_level > 0) + dbg_log (_("Invalid numeric uid \"%s\"!"), (char *) key); + + errno = EINVAL; + return; + } + + union keytype u = { .u = uid }; + + addpwbyX (db, fd, req, u, key, c_uid, NULL, NULL); +} + + +time_t +readdpwbyuid (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + char *ep; + uid_t uid = strtoul (db->data + he->key, &ep, 10); + + /* Since the key has been added before it must be OK. */ + assert (*(db->data + he->key) != '\0' && *ep == '\0'); + + request_header req = + { + .type = GETPWBYUID, + .key_len = he->len + }; + union keytype u = { .u = uid }; + + return addpwbyX (db, -1, &req, u, db->data + he->key, he->owner, he, dh); +} diff --git a/REORG.TODO/nscd/res_hconf.c b/REORG.TODO/nscd/res_hconf.c new file mode 100644 index 0000000000..14b0e300bc --- /dev/null +++ b/REORG.TODO/nscd/res_hconf.c @@ -0,0 +1,13 @@ +/* Add the include here so that we can redefine __fxprintf. */ +#include <stdio.h> + +/* Rename symbols for protected names used in libc itself. */ +#define __ioctl ioctl +#define __socket socket +#define __strchrnul strchrnul +#define __strncasecmp strncasecmp + +#define __fxprintf(args...) /* ignore */ + + +#include "../resolv/res_hconf.c" diff --git a/REORG.TODO/nscd/selinux.c b/REORG.TODO/nscd/selinux.c new file mode 100644 index 0000000000..f7bcd8e4c8 --- /dev/null +++ b/REORG.TODO/nscd/selinux.c @@ -0,0 +1,453 @@ +/* SELinux access controls for nscd. + Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Matthew Rickard <mjricka@epoch.ncsc.mil>, 2004. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include <error.h> +#include <errno.h> +#include <libintl.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <syslog.h> +#include <unistd.h> +#include <sys/prctl.h> +#include <selinux/avc.h> +#include <selinux/selinux.h> +#ifdef HAVE_LIBAUDIT +# include <libaudit.h> +#endif + +#include "dbg_log.h" +#include "selinux.h" + + +#ifdef HAVE_SELINUX +/* Global variable to tell if the kernel has SELinux support. */ +int selinux_enabled; + +/* Define mappings of request type to AVC permission name. */ +static const char *perms[LASTREQ] = +{ + [GETPWBYNAME] = "getpwd", + [GETPWBYUID] = "getpwd", + [GETGRBYNAME] = "getgrp", + [GETGRBYGID] = "getgrp", + [GETHOSTBYNAME] = "gethost", + [GETHOSTBYNAMEv6] = "gethost", + [GETHOSTBYADDR] = "gethost", + [GETHOSTBYADDRv6] = "gethost", + [SHUTDOWN] = "admin", + [GETSTAT] = "getstat", + [INVALIDATE] = "admin", + [GETFDPW] = "shmempwd", + [GETFDGR] = "shmemgrp", + [GETFDHST] = "shmemhost", + [GETAI] = "gethost", + [INITGROUPS] = "getgrp", + [GETSERVBYNAME] = "getserv", + [GETSERVBYPORT] = "getserv", + [GETFDSERV] = "shmemserv", + [GETNETGRENT] = "getnetgrp", + [INNETGR] = "getnetgrp", + [GETFDNETGR] = "shmemnetgrp", +}; + +/* Store an entry ref to speed AVC decisions. */ +static struct avc_entry_ref aeref; + +/* Thread to listen for SELinux status changes via netlink. */ +static pthread_t avc_notify_thread; + +#ifdef HAVE_LIBAUDIT +/* Prototype for supporting the audit daemon */ +static void log_callback (const char *fmt, ...); +#endif + +/* Prototypes for AVC callback functions. */ +static void *avc_create_thread (void (*run) (void)); +static void avc_stop_thread (void *thread); +static void *avc_alloc_lock (void); +static void avc_get_lock (void *lock); +static void avc_release_lock (void *lock); +static void avc_free_lock (void *lock); + +/* AVC callback structures for use in avc_init. */ +static const struct avc_log_callback log_cb = +{ +#ifdef HAVE_LIBAUDIT + .func_log = log_callback, +#else + .func_log = dbg_log, +#endif + .func_audit = NULL +}; +static const struct avc_thread_callback thread_cb = +{ + .func_create_thread = avc_create_thread, + .func_stop_thread = avc_stop_thread +}; +static const struct avc_lock_callback lock_cb = +{ + .func_alloc_lock = avc_alloc_lock, + .func_get_lock = avc_get_lock, + .func_release_lock = avc_release_lock, + .func_free_lock = avc_free_lock +}; + +#ifdef HAVE_LIBAUDIT +/* The audit system's netlink socket descriptor */ +static int audit_fd = -1; + +/* When an avc denial occurs, log it to audit system */ +static void +log_callback (const char *fmt, ...) +{ + if (audit_fd >= 0) + { + va_list ap; + va_start (ap, fmt); + + char *buf; + int e = vasprintf (&buf, fmt, ap); + if (e < 0) + { + buf = alloca (BUFSIZ); + vsnprintf (buf, BUFSIZ, fmt, ap); + } + + /* FIXME: need to attribute this to real user, using getuid for now */ + audit_log_user_avc_message (audit_fd, AUDIT_USER_AVC, buf, NULL, NULL, + NULL, getuid ()); + + if (e >= 0) + free (buf); + + va_end (ap); + } +} + +/* Initialize the connection to the audit system */ +static void +audit_init (void) +{ + audit_fd = audit_open (); + if (audit_fd < 0 + /* If kernel doesn't support audit, bail out */ + && errno != EINVAL && errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT) + dbg_log (_("Failed opening connection to the audit subsystem: %m")); +} + + +# ifdef HAVE_LIBCAP +static const cap_value_t new_cap_list[] = + { CAP_AUDIT_WRITE }; +# define nnew_cap_list (sizeof (new_cap_list) / sizeof (new_cap_list[0])) +static const cap_value_t tmp_cap_list[] = + { CAP_AUDIT_WRITE, CAP_SETUID, CAP_SETGID }; +# define ntmp_cap_list (sizeof (tmp_cap_list) / sizeof (tmp_cap_list[0])) + +cap_t +preserve_capabilities (void) +{ + if (getuid () != 0) + /* Not root, then we cannot preserve anything. */ + return NULL; + + if (prctl (PR_SET_KEEPCAPS, 1) == -1) + { + dbg_log (_("Failed to set keep-capabilities")); + do_exit (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed")); + /* NOTREACHED */ + } + + cap_t tmp_caps = cap_init (); + cap_t new_caps = NULL; + if (tmp_caps != NULL) + new_caps = cap_init (); + + if (tmp_caps == NULL || new_caps == NULL) + { + if (tmp_caps != NULL) + cap_free (tmp_caps); + + dbg_log (_("Failed to initialize drop of capabilities")); + do_exit (EXIT_FAILURE, 0, _("cap_init failed")); + } + + /* There is no reason why these should not work. */ + cap_set_flag (new_caps, CAP_PERMITTED, nnew_cap_list, + (cap_value_t *) new_cap_list, CAP_SET); + cap_set_flag (new_caps, CAP_EFFECTIVE, nnew_cap_list, + (cap_value_t *) new_cap_list, CAP_SET); + + cap_set_flag (tmp_caps, CAP_PERMITTED, ntmp_cap_list, + (cap_value_t *) tmp_cap_list, CAP_SET); + cap_set_flag (tmp_caps, CAP_EFFECTIVE, ntmp_cap_list, + (cap_value_t *) tmp_cap_list, CAP_SET); + + int res = cap_set_proc (tmp_caps); + + cap_free (tmp_caps); + + if (__glibc_unlikely (res != 0)) + { + cap_free (new_caps); + dbg_log (_("Failed to drop capabilities")); + do_exit (EXIT_FAILURE, 0, _("cap_set_proc failed")); + } + + return new_caps; +} + +void +install_real_capabilities (cap_t new_caps) +{ + /* If we have no capabilities there is nothing to do here. */ + if (new_caps == NULL) + return; + + if (cap_set_proc (new_caps)) + { + cap_free (new_caps); + dbg_log (_("Failed to drop capabilities")); + do_exit (EXIT_FAILURE, 0, _("cap_set_proc failed")); + /* NOTREACHED */ + } + + cap_free (new_caps); + + if (prctl (PR_SET_KEEPCAPS, 0) == -1) + { + dbg_log (_("Failed to unset keep-capabilities")); + do_exit (EXIT_FAILURE, errno, _("prctl(KEEPCAPS) failed")); + /* NOTREACHED */ + } +} +# endif /* HAVE_LIBCAP */ +#endif /* HAVE_LIBAUDIT */ + +/* Determine if we are running on an SELinux kernel. Set selinux_enabled + to the result. */ +void +nscd_selinux_enabled (int *selinux_enabled) +{ + *selinux_enabled = is_selinux_enabled (); + if (*selinux_enabled < 0) + { + dbg_log (_("Failed to determine if kernel supports SELinux")); + do_exit (EXIT_FAILURE, 0, NULL); + } +} + + +/* Create thread for AVC netlink notification. */ +static void * +avc_create_thread (void (*run) (void)) +{ + int rc; + + rc = + pthread_create (&avc_notify_thread, NULL, (void *(*) (void *)) run, NULL); + if (rc != 0) + do_exit (EXIT_FAILURE, rc, _("Failed to start AVC thread")); + + return &avc_notify_thread; +} + + +/* Stop AVC netlink thread. */ +static void +avc_stop_thread (void *thread) +{ + pthread_cancel (*(pthread_t *) thread); +} + + +/* Allocate a new AVC lock. */ +static void * +avc_alloc_lock (void) +{ + pthread_mutex_t *avc_mutex; + + avc_mutex = malloc (sizeof (pthread_mutex_t)); + if (avc_mutex == NULL) + do_exit (EXIT_FAILURE, errno, _("Failed to create AVC lock")); + pthread_mutex_init (avc_mutex, NULL); + + return avc_mutex; +} + + +/* Acquire an AVC lock. */ +static void +avc_get_lock (void *lock) +{ + pthread_mutex_lock (lock); +} + + +/* Release an AVC lock. */ +static void +avc_release_lock (void *lock) +{ + pthread_mutex_unlock (lock); +} + + +/* Free an AVC lock. */ +static void +avc_free_lock (void *lock) +{ + pthread_mutex_destroy (lock); + free (lock); +} + + +/* Initialize the user space access vector cache (AVC) for NSCD along with + log/thread/lock callbacks. */ +void +nscd_avc_init (void) +{ + avc_entry_ref_init (&aeref); + + if (avc_init ("avc", NULL, &log_cb, &thread_cb, &lock_cb) < 0) + do_exit (EXIT_FAILURE, errno, _("Failed to start AVC")); + else + dbg_log (_("Access Vector Cache (AVC) started")); +#ifdef HAVE_LIBAUDIT + audit_init (); +#endif +} + + +/* Check the permission from the caller (via getpeercon) to nscd. + Returns 0 if access is allowed, 1 if denied, and -1 on error. + + The SELinux policy, enablement, and permission bits are all dynamic and the + caching done by glibc is not entirely correct. This nscd support should be + rewritten to use selinux_check_permission. A rewrite is risky though and + requires some refactoring. Currently we use symbolic mappings instead of + compile time constants (which SELinux upstream says are going away), and we + use security_deny_unknown to determine what to do if selinux-policy* doesn't + have a definition for the the permission or object class we are looking + up. */ +int +nscd_request_avc_has_perm (int fd, request_type req) +{ + /* Initialize to NULL so we know what to free in case of failure. */ + security_context_t scon = NULL; + security_context_t tcon = NULL; + security_id_t ssid = NULL; + security_id_t tsid = NULL; + int rc = -1; + security_class_t sc_nscd; + access_vector_t perm; + int avc_deny_unknown; + + /* Check if SELinux denys or allows unknown object classes + and permissions. It is 0 if they are allowed, 1 if they + are not allowed and -1 on error. */ + if ((avc_deny_unknown = security_deny_unknown ()) == -1) + dbg_log (_("Error querying policy for undefined object classes " + "or permissions.")); + + /* Get the security class for nscd. If this fails we will likely be + unable to do anything unless avc_deny_unknown is 0. */ + sc_nscd = string_to_security_class ("nscd"); + if (sc_nscd == 0 && avc_deny_unknown == 1) + dbg_log (_("Error getting security class for nscd.")); + + /* Convert permission to AVC bits. */ + perm = string_to_av_perm (sc_nscd, perms[req]); + if (perm == 0 && avc_deny_unknown == 1) + dbg_log (_("Error translating permission name " + "\"%s\" to access vector bit."), perms[req]); + + /* If the nscd security class was not found or perms were not + found and AVC does not deny unknown values then allow it. */ + if ((sc_nscd == 0 || perm == 0) && avc_deny_unknown == 0) + return 0; + + if (getpeercon (fd, &scon) < 0) + { + dbg_log (_("Error getting context of socket peer")); + goto out; + } + if (getcon (&tcon) < 0) + { + dbg_log (_("Error getting context of nscd")); + goto out; + } + if (avc_context_to_sid (scon, &ssid) < 0 + || avc_context_to_sid (tcon, &tsid) < 0) + { + dbg_log (_("Error getting sid from context")); + goto out; + } + + /* The SELinux API for avc_has_perm conflates access denied and error into + the return code -1, while nscd_request_avs_has_perm has distinct error + (-1) and denied (1) return codes. We map the avc_has_perm access denied or + error into an access denied at the nscd interface level (we do accurately + report error for the getpeercon, getcon, and avc_context_to_sid interfaces + used above). */ + rc = avc_has_perm (ssid, tsid, sc_nscd, perm, &aeref, NULL) < 0; + +out: + if (scon) + freecon (scon); + if (tcon) + freecon (tcon); + if (ssid) + sidput (ssid); + if (tsid) + sidput (tsid); + + return rc; +} + + +/* Wrapper to get AVC statistics. */ +void +nscd_avc_cache_stats (struct avc_cache_stats *cstats) +{ + avc_cache_stats (cstats); +} + + +/* Print the AVC statistics to stdout. */ +void +nscd_avc_print_stats (struct avc_cache_stats *cstats) +{ + printf (_("\nSELinux AVC Statistics:\n\n" + "%15u entry lookups\n" + "%15u entry hits\n" + "%15u entry misses\n" + "%15u entry discards\n" + "%15u CAV lookups\n" + "%15u CAV hits\n" + "%15u CAV probes\n" + "%15u CAV misses\n"), + cstats->entry_lookups, cstats->entry_hits, cstats->entry_misses, + cstats->entry_discards, cstats->cav_lookups, cstats->cav_hits, + cstats->cav_probes, cstats->cav_misses); +} + +#endif /* HAVE_SELINUX */ diff --git a/REORG.TODO/nscd/selinux.h b/REORG.TODO/nscd/selinux.h new file mode 100644 index 0000000000..052d62a6fb --- /dev/null +++ b/REORG.TODO/nscd/selinux.h @@ -0,0 +1,61 @@ +/* Header for nscd SELinux access controls. + Copyright (C) 2004-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Matthew Rickard <mjricka@epoch.ncsc.mil>, 2004. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#ifndef _SELINUX_H +#define _SELINUX_H 1 + +#include "nscd.h" +#ifdef HAVE_LIBCAP +# include <sys/capability.h> +#endif + +#ifdef HAVE_SELINUX +/* Global variable to tell if the kernel has SELinux support. */ +extern int selinux_enabled; + +/* Define this for AVC stat usage. */ +struct avc_cache_stats; + +/* Initialize the userspace AVC. */ +extern void nscd_avc_init (void); +/* Determine if we are running on an SELinux kernel. */ +extern void nscd_selinux_enabled (int *selinux_enabled); +/* Check if the client has permission for the request type. */ +extern int nscd_request_avc_has_perm (int fd, request_type req); +/* Initialize AVC statistic information. */ +extern void nscd_avc_cache_stats (struct avc_cache_stats *cstats); +/* Display statistics on AVC usage. */ +extern void nscd_avc_print_stats (struct avc_cache_stats *cstats); + +# ifdef HAVE_LIBCAP +/* Preserve capabilities to connect to the audit daemon. */ +extern cap_t preserve_capabilities (void); +/* Install final capabilities. */ +extern void install_real_capabilities (cap_t new_caps); +# endif +#else +# define selinux_enabled 0 +# define nscd_avc_init() (void) 0 +# define nscd_selinux_enabled(selinux_enabled) (void) 0 +# define nscd_request_avc_has_perm(fd, req) 0 +# define nscd_avc_cache_stats(cstats) (void) 0 +# define nscd_avc_print_stats(cstats) (void) 0 +#endif /* HAVE_SELINUX */ + +#endif /* _SELINUX_H */ diff --git a/REORG.TODO/nscd/servicescache.c b/REORG.TODO/nscd/servicescache.c new file mode 100644 index 0000000000..131ba6ddcc --- /dev/null +++ b/REORG.TODO/nscd/servicescache.c @@ -0,0 +1,474 @@ +/* Cache handling for services lookup. + Copyright (C) 2007-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@drepper.com>, 2007. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#include <alloca.h> +#include <assert.h> +#include <errno.h> +#include <libintl.h> +#include <netdb.h> +#include <unistd.h> +#include <stdint.h> +#include <sys/mman.h> +#include <kernel-features.h> + +#include "nscd.h" +#include "dbg_log.h" + + +/* This is the standard reply in case the service is disabled. */ +static const serv_response_header disabled = +{ + .version = NSCD_VERSION, + .found = -1, + .s_name_len = 0, + .s_proto_len = 0, + .s_aliases_cnt = 0, + .s_port = -1 +}; + +/* This is the struct describing how to write this record. */ +const struct iovec serv_iov_disabled = +{ + .iov_base = (void *) &disabled, + .iov_len = sizeof (disabled) +}; + + +/* This is the standard reply in case we haven't found the dataset. */ +static const serv_response_header notfound = +{ + .version = NSCD_VERSION, + .found = 0, + .s_name_len = 0, + .s_proto_len = 0, + .s_aliases_cnt = 0, + .s_port = -1 +}; + + +static time_t +cache_addserv (struct database_dyn *db, int fd, request_header *req, + const void *key, struct servent *serv, uid_t owner, + struct hashentry *const he, struct datahead *dh, int errval) +{ + bool all_written = true; + ssize_t total; + time_t t = time (NULL); + + /* We allocate all data in one memory block: the iov vector, + the response header and the dataset itself. */ + struct dataset + { + struct datahead head; + serv_response_header resp; + char strdata[0]; + } *dataset; + + assert (offsetof (struct dataset, resp) == offsetof (struct datahead, data)); + + time_t timeout = MAX_TIMEOUT_VALUE; + if (serv == NULL) + { + if (he != NULL && errval == EAGAIN) + { + /* If we have an old record available but cannot find one + now because the service is not available we keep the old + record and make sure it does not get removed. */ + if (reload_count != UINT_MAX) + /* Do not reset the value if we never not reload the record. */ + dh->nreloads = reload_count - 1; + + /* Reload with the same time-to-live value. */ + timeout = dh->timeout = t + db->postimeout; + + total = 0; + } + else + { + /* We have no data. This means we send the standard reply for this + case. */ + total = sizeof (notfound); + + if (fd != -1 + && TEMP_FAILURE_RETRY (send (fd, ¬found, total, + MSG_NOSIGNAL)) != total) + all_written = false; + + /* If we have a transient error or cannot permanently store + the result, so be it. */ + if (errval == EAGAIN || __builtin_expect (db->negtimeout == 0, 0)) + { + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + else if ((dataset = mempool_alloc (db, (sizeof (struct dataset) + + req->key_len), 1)) != NULL) + { + timeout = datahead_init_neg (&dataset->head, + (sizeof (struct dataset) + + req->key_len), total, + db->negtimeout); + + /* This is the reply. */ + memcpy (&dataset->resp, ¬found, total); + + /* Copy the key data. */ + memcpy (dataset->strdata, key, req->key_len); + + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + + sizeof (struct dataset) + req->key_len, MS_ASYNC); + } + + (void) cache_add (req->type, &dataset->strdata, req->key_len, + &dataset->head, true, db, owner, he == NULL); + + pthread_rwlock_unlock (&db->lock); + + /* Mark the old entry as obsolete. */ + if (dh != NULL) + dh->usable = false; + } + } + } + else + { + /* Determine the I/O structure. */ + size_t s_name_len = strlen (serv->s_name) + 1; + size_t s_proto_len = strlen (serv->s_proto) + 1; + uint32_t *s_aliases_len; + size_t s_aliases_cnt; + char *aliases; + char *cp; + size_t cnt; + + /* Determine the number of aliases. */ + s_aliases_cnt = 0; + for (cnt = 0; serv->s_aliases[cnt] != NULL; ++cnt) + ++s_aliases_cnt; + /* Determine the length of all aliases. */ + s_aliases_len = (uint32_t *) alloca (s_aliases_cnt * sizeof (uint32_t)); + total = 0; + for (cnt = 0; cnt < s_aliases_cnt; ++cnt) + { + s_aliases_len[cnt] = strlen (serv->s_aliases[cnt]) + 1; + total += s_aliases_len[cnt]; + } + + total += (offsetof (struct dataset, strdata) + + s_name_len + + s_proto_len + + s_aliases_cnt * sizeof (uint32_t)); + + /* If we refill the cache, first assume the reconrd did not + change. Allocate memory on the cache since it is likely + discarded anyway. If it turns out to be necessary to have a + new record we can still allocate real memory. */ + bool alloca_used = false; + dataset = NULL; + + if (he == NULL) + dataset = (struct dataset *) mempool_alloc (db, total + req->key_len, + 1); + + if (dataset == NULL) + { + /* We cannot permanently add the result in the moment. But + we can provide the result as is. Store the data in some + temporary memory. */ + dataset = (struct dataset *) alloca (total + req->key_len); + + /* We cannot add this record to the permanent database. */ + alloca_used = true; + } + + timeout = datahead_init_pos (&dataset->head, total + req->key_len, + total - offsetof (struct dataset, resp), + he == NULL ? 0 : dh->nreloads + 1, + db->postimeout); + + dataset->resp.version = NSCD_VERSION; + dataset->resp.found = 1; + dataset->resp.s_name_len = s_name_len; + dataset->resp.s_proto_len = s_proto_len; + dataset->resp.s_port = serv->s_port; + dataset->resp.s_aliases_cnt = s_aliases_cnt; + + cp = dataset->strdata; + + cp = mempcpy (cp, serv->s_name, s_name_len); + cp = mempcpy (cp, serv->s_proto, s_proto_len); + cp = mempcpy (cp, s_aliases_len, s_aliases_cnt * sizeof (uint32_t)); + + /* Then the aliases. */ + aliases = cp; + for (cnt = 0; cnt < s_aliases_cnt; ++cnt) + cp = mempcpy (cp, serv->s_aliases[cnt], s_aliases_len[cnt]); + + assert (cp + == dataset->strdata + total - offsetof (struct dataset, + strdata)); + + char *key_copy = memcpy (cp, key, req->key_len); + + /* Now we can determine whether on refill we have to create a new + record or not. */ + if (he != NULL) + { + assert (fd == -1); + + if (total + req->key_len == dh->allocsize + && total - offsetof (struct dataset, resp) == dh->recsize + && memcmp (&dataset->resp, dh->data, + dh->allocsize - offsetof (struct dataset, resp)) == 0) + { + /* The data has not changed. We will just bump the + timeout value. Note that the new record has been + allocated on the stack and need not be freed. */ + dh->timeout = dataset->head.timeout; + ++dh->nreloads; + } + else + { + /* We have to create a new record. Just allocate + appropriate memory and copy it. */ + struct dataset *newp + = (struct dataset *) mempool_alloc (db, total + req->key_len, + 1); + if (newp != NULL) + { + /* Adjust pointers into the memory block. */ + aliases = (char *) newp + (aliases - (char *) dataset); + assert (key_copy != NULL); + key_copy = (char *) newp + (key_copy - (char *) dataset); + + dataset = memcpy (newp, dataset, total + req->key_len); + alloca_used = false; + } + + /* Mark the old record as obsolete. */ + dh->usable = false; + } + } + else + { + /* We write the dataset before inserting it to the database + since while inserting this thread might block and so would + unnecessarily keep the receiver waiting. */ + assert (fd != -1); + +#ifdef HAVE_SENDFILE + if (__builtin_expect (db->mmap_used, 1) && !alloca_used) + { + assert (db->wr_fd != -1); + assert ((char *) &dataset->resp > (char *) db->data); + assert ((char *) dataset - (char *) db->head + + total + <= (sizeof (struct database_pers_head) + + db->head->module * sizeof (ref_t) + + db->head->data_size)); + ssize_t written = sendfileall (fd, db->wr_fd, + (char *) &dataset->resp + - (char *) db->head, + dataset->head.recsize); + if (written != dataset->head.recsize) + { +# ifndef __ASSUME_SENDFILE + if (written == -1 && errno == ENOSYS) + goto use_write; +# endif + all_written = false; + } + } + else +# ifndef __ASSUME_SENDFILE + use_write: +# endif +#endif + if (writeall (fd, &dataset->resp, dataset->head.recsize) + != dataset->head.recsize) + all_written = false; + } + + /* Add the record to the database. But only if it has not been + stored on the stack. */ + if (! alloca_used) + { + /* If necessary, we also propagate the data to disk. */ + if (db->persistent) + { + // XXX async OK? + uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1; + msync ((void *) pval, + ((uintptr_t) dataset & pagesize_m1) + + total + req->key_len, MS_ASYNC); + } + + (void) cache_add (req->type, key_copy, req->key_len, + &dataset->head, true, db, owner, he == NULL); + + pthread_rwlock_unlock (&db->lock); + } + } + + if (__builtin_expect (!all_written, 0) && debug_level > 0) + { + char buf[256]; + dbg_log (_("short write in %s: %s"), __FUNCTION__, + strerror_r (errno, buf, sizeof (buf))); + } + + return timeout; +} + + +static int +lookup (int type, char *key, struct servent *resultbufp, char *buffer, + size_t buflen, struct servent **serv) +{ + char *proto = strrchr (key, '/'); + if (proto != NULL && proto != key) + { + key = strndupa (key, proto - key); + if (proto[1] == '\0') + proto = NULL; + else + ++proto; + } + + if (type == GETSERVBYNAME) + return __getservbyname_r (key, proto, resultbufp, buffer, buflen, serv); + + assert (type == GETSERVBYPORT); + return __getservbyport_r (atol (key), proto, resultbufp, buffer, buflen, + serv); +} + + +static time_t +addservbyX (struct database_dyn *db, int fd, request_header *req, + char *key, uid_t uid, struct hashentry *he, struct datahead *dh) +{ + /* Search for the entry matching the key. Please note that we don't + look again in the table whether the dataset is now available. We + simply insert it. It does not matter if it is in there twice. The + pruning function only will look at the timestamp. */ + size_t buflen = 1024; + char *buffer = (char *) alloca (buflen); + struct servent resultbuf; + struct servent *serv; + bool use_malloc = false; + int errval = 0; + + if (__glibc_unlikely (debug_level > 0)) + { + if (he == NULL) + dbg_log (_("Haven't found \"%s\" in services cache!"), key); + else + dbg_log (_("Reloading \"%s\" in services cache!"), key); + } + + while (lookup (req->type, key, &resultbuf, buffer, buflen, &serv) != 0 + && (errval = errno) == ERANGE) + { + errno = 0; + + if (__glibc_unlikely (buflen > 32768)) + { + char *old_buffer = buffer; + buflen *= 2; + buffer = (char *) realloc (use_malloc ? buffer : NULL, buflen); + if (buffer == NULL) + { + /* We ran out of memory. We cannot do anything but + sending a negative response. In reality this should + never happen. */ + serv = NULL; + buffer = old_buffer; + + /* We set the error to indicate this is (possibly) a + temporary error and that it does not mean the entry + is not available at all. */ + errval = EAGAIN; + break; + } + use_malloc = true; + } + else + /* Allocate a new buffer on the stack. If possible combine it + with the previously allocated buffer. */ + buffer = (char *) extend_alloca (buffer, buflen, 2 * buflen); + } + + time_t timeout = cache_addserv (db, fd, req, key, serv, uid, he, dh, errval); + + if (use_malloc) + free (buffer); + + return timeout; +} + + +void +addservbyname (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + addservbyX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdservbyname (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETSERVBYNAME, + .key_len = he->len + }; + + return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} + + +void +addservbyport (struct database_dyn *db, int fd, request_header *req, + void *key, uid_t uid) +{ + addservbyX (db, fd, req, key, uid, NULL, NULL); +} + + +time_t +readdservbyport (struct database_dyn *db, struct hashentry *he, + struct datahead *dh) +{ + request_header req = + { + .type = GETSERVBYPORT, + .key_len = he->len + }; + + return addservbyX (db, -1, &req, db->data + he->key, he->owner, he, dh); +} |