| /* Inner loops of cache daemon. |
| Copyright (C) 1998-2003, 2004, 2005, 2006 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 version 2 as |
| published by the Free Software Foundation. |
| |
| 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, write to the Free Software Foundation, |
| Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ |
| |
| #include <alloca.h> |
| #include <assert.h> |
| #include <atomic.h> |
| #include <error.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <grp.h> |
| #include <libintl.h> |
| #include <pthread.h> |
| #include <pwd.h> |
| #include <resolv.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #ifdef HAVE_EPOLL |
| # include <sys/epoll.h> |
| #endif |
| #include <sys/mman.h> |
| #include <sys/param.h> |
| #include <sys/poll.h> |
| #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" |
| #ifdef HAVE_SENDFILE |
| # include <kernel-features.h> |
| #endif |
| |
| |
| /* Wrapper functions with error checking for standard functions. */ |
| extern void *xmalloc (size_t n); |
| extern void *xcalloc (size_t n, size_t s); |
| extern void *xrealloc (void *o, size_t n); |
| |
| /* 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 *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" |
| }; |
| |
| /* The control data structures for the services. */ |
| struct database_dyn dbs[lastdb] = |
| { |
| [pwddb] = { |
| .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP, |
| .prunelock = PTHREAD_MUTEX_INITIALIZER, |
| .enabled = 0, |
| .check_file = 1, |
| .persistent = 0, |
| .propagate = 1, |
| .shared = 0, |
| .max_db_size = DEFAULT_MAX_DB_SIZE, |
| .filename = "/etc/passwd", |
| .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, |
| .prunelock = PTHREAD_MUTEX_INITIALIZER, |
| .enabled = 0, |
| .check_file = 1, |
| .persistent = 0, |
| .propagate = 1, |
| .shared = 0, |
| .max_db_size = DEFAULT_MAX_DB_SIZE, |
| .filename = "/etc/group", |
| .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, |
| .prunelock = PTHREAD_MUTEX_INITIALIZER, |
| .enabled = 0, |
| .check_file = 1, |
| .persistent = 0, |
| .propagate = 0, /* Not used. */ |
| .shared = 0, |
| .max_db_size = DEFAULT_MAX_DB_SIZE, |
| .filename = "/etc/hosts", |
| .db_filename = _PATH_NSCD_HOSTS_DB, |
| .disabled_iov = &hst_iov_disabled, |
| .postimeout = 3600, |
| .negtimeout = 20, |
| .wr_fd = -1, |
| .ro_fd = -1, |
| .mmap_used = false |
| } |
| }; |
| |
| |
| /* Mapping of request type to database. */ |
| static struct database_dyn *const serv2db[LASTREQ] = |
| { |
| [GETPWBYNAME] = &dbs[pwddb], |
| [GETPWBYUID] = &dbs[pwddb], |
| [GETGRBYNAME] = &dbs[grpdb], |
| [GETGRBYGID] = &dbs[grpdb], |
| [GETHOSTBYNAME] = &dbs[hstdb], |
| [GETHOSTBYNAMEv6] = &dbs[hstdb], |
| [GETHOSTBYADDR] = &dbs[hstdb], |
| [GETHOSTBYADDRv6] = &dbs[hstdb], |
| [GETFDPW] = &dbs[pwddb], |
| [GETFDGR] = &dbs[grpdb], |
| [GETFDHST] = &dbs[hstdb], |
| [GETAI] = &dbs[hstdb], |
| [INITGROUPS] = &dbs[grpdb] |
| }; |
| |
| |
| /* Number of seconds between two cache pruning runs. */ |
| #define CACHE_PRUNE_INTERVAL 15 |
| |
| |
| /* 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; |
| |
| /* 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, |
| #if SEPARATE_KEY |
| use_key = 2, |
| use_key_begin = use_key | use_begin, |
| use_key_end = use_key | use_end, |
| use_key_first = use_key_begin | use_first, |
| #endif |
| 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); |
| |
| 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 (readhead != NULL && 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) |
| || (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 work = head->array[cnt]; |
| |
| 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 |
| || serv2db[here->type] != &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) |
| { |
| #if SEPARATE_KEY |
| /* If keys can appear outside of data, this should be done |
| instead. But gc doesn't mark the data in that case. */ |
| if (! check_use (data, head->first_free, usemap, |
| use_key | (here->first ? use_first : 0), |
| here->key, here->len)) |
| #endif |
| goto fail; |
| } |
| |
| work = here->next; |
| } |
| } |
| |
| 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 SEPARATE_KEY |
| if (usemap[idx] == use_key_begin) |
| goto fail; |
| #endif |
| 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 = 2 * lastdb; |
| |
| 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); |
| if (fd != -1) |
| { |
| 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: |
| dbg_log (_("invalid persistent database file \"%s\": %s"), |
| dbs[cnt].db_filename, strerror (errno)); |
| 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. Remove the old file. */ |
| unlink (dbs[cnt].db_filename); |
| } |
| else if (head.header_size != (int) sizeof (head)) |
| { |
| dbg_log (_("invalid persistent database file \"%s\": %s"), |
| dbs[cnt].db_filename, |
| _("header size does not match")); |
| unlink (dbs[cnt].db_filename); |
| } |
| else if ((total = (sizeof (head) |
| + roundup (head.module * sizeof (ref_t), |
| ALIGN) |
| + head.data_size)) |
| > st.st_size |
| || total < sizeof (head)) |
| { |
| dbg_log (_("invalid persistent database file \"%s\": %s"), |
| dbs[cnt].db_filename, |
| _("file size does not match")); |
| unlink (dbs[cnt].db_filename); |
| } |
| /* 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; |
| else if (!verify_persistent_db (mem, &head, cnt)) |
| { |
| munmap (mem, total); |
| dbg_log (_("invalid persistent database file \"%s\": %s"), |
| dbs[cnt].db_filename, |
| _("verification failed")); |
| unlink (dbs[cnt].db_filename); |
| } |
| 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); |
| 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); |
| } |
| } |
| |
| 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, |
| S_IRUSR | S_IWUSR); |
| if (fd != -1 && dbs[cnt].shared) |
| ro_fd = open (dbs[cnt].db_filename, O_RDONLY); |
| } |
| else |
| { |
| char fname[] = _PATH_NSCD_XYZ_DB_TMP; |
| fd = mkstemp (fname); |
| |
| /* 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); |
| |
| 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); |
| // XXX Correct way to terminate? |
| exit (1); |
| } |
| |
| 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, initialiye the hash |
| table. So that if we get interrupted if 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 (paranoia |
| && ((dbs[cnt].wr_fd != -1 |
| && fcntl (dbs[cnt].wr_fd, F_SETFD, FD_CLOEXEC) == -1) |
| || (dbs[cnt].ro_fd != -1 |
| && fcntl (dbs[cnt].ro_fd, F_SETFD, FD_CLOEXEC) == -1))) |
| { |
| dbg_log (_("\ |
| cannot set socket to close on exec: %s; disabling paranoia mode"), |
| strerror (errno)); |
| paranoia = 0; |
| } |
| |
| if (dbs[cnt].head == NULL) |
| { |
| /* We do not use the persistent database. Just |
| 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); |
| } |
| |
| if (dbs[cnt].check_file) |
| { |
| /* We need the modification date of the file. */ |
| struct stat64 st; |
| |
| if (stat64 (dbs[cnt].filename, &st) < 0) |
| { |
| /* We cannot stat() the file, disable file checking. */ |
| dbg_log (_("cannot stat() file `%s': %s"), |
| dbs[cnt].filename, strerror (errno)); |
| dbs[cnt].check_file = 0; |
| } |
| else |
| dbs[cnt].file_mtime = st.st_mtime; |
| } |
| } |
| |
| /* Create the socket. */ |
| sock = socket (AF_UNIX, SOCK_STREAM, 0); |
| if (sock < 0) |
| { |
| dbg_log (_("cannot open socket: %s"), strerror (errno)); |
| exit (errno == EACCES ? 4 : 1); |
| } |
| /* 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)); |
| exit (errno == EACCES ? 4 : 1); |
| } |
| |
| /* We don't want to get stuck on accept. */ |
| int fl = fcntl (sock, F_GETFL); |
| if (fl == -1 || fcntl (sock, F_SETFL, fl | O_NONBLOCK) == -1) |
| { |
| dbg_log (_("cannot change socket to nonblocking mode: %s"), |
| strerror (errno)); |
| exit (1); |
| } |
| |
| /* The descriptor needs to be closed on exec. */ |
| if (paranoia && fcntl (sock, F_SETFD, FD_CLOEXEC) == -1) |
| { |
| dbg_log (_("cannot set socket to close on exec: %s"), |
| strerror (errno)); |
| exit (1); |
| } |
| |
| /* Set permissions for the socket. */ |
| chmod (_PATH_NSCDSOCKET, DEFFILEMODE); |
| |
| /* Set the socket up to accept connections. */ |
| if (listen (sock, SOMAXCONN) < 0) |
| { |
| dbg_log (_("cannot enable socket to accept connections: %s"), |
| strerror (errno)); |
| exit (1); |
| } |
| |
| /* Change to unprivileged uid/gid/groups if specifed in config file */ |
| if (server_user != NULL) |
| finish_drop_privileges (); |
| } |
| |
| |
| /* Close the connections. */ |
| void |
| close_sockets (void) |
| { |
| close (sock); |
| } |
| |
| |
| static void |
| invalidate_cache (char *key, int fd) |
| { |
| dbtype number; |
| int32_t resp; |
| |
| if (strcmp (key, "passwd") == 0) |
| number = pwddb; |
| else if (strcmp (key, "group") == 0) |
| number = grpdb; |
| else if (__builtin_expect (strcmp (key, "hosts"), 0) == 0) |
| { |
| number = hstdb; |
| |
| /* Re-initialize the resolver. resolv.conf might have changed. */ |
| res_init (); |
| } |
| else |
| { |
| resp = EINVAL; |
| writeall (fd, &resp, sizeof (resp)); |
| return; |
| } |
| |
| if (dbs[number].enabled) |
| prune_cache (&dbs[number], LONG_MAX, fd); |
| 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. */ |
| struct iovec iov[1]; |
| iov[0].iov_base = key; |
| iov[0].iov_len = strlen (key) + 1; |
| |
| /* 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 = 1, |
| .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 *) CMSG_DATA (cmsg) = 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 (__builtin_expect (debug_level > 0, 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) |
| { |
| 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; |
| } |
| |
| /* Make the SELinux check before we go on to the standard checks. We |
| need to verify that the request type is valid, since it has not |
| yet been checked at this point. */ |
| if (selinux_enabled |
| && __builtin_expect (req->type, GETPWBYNAME) >= GETPWBYNAME |
| && __builtin_expect (req->type, LASTREQ) < LASTREQ |
| && nscd_request_avc_has_perm (fd, req->type) != 0) |
| return; |
| |
| struct database_dyn *db = serv2db[req->type]; |
| |
| // XXX Clean up so that each new command need not introduce a |
| // XXX new conditional. |
| if ((__builtin_expect (req->type, GETPWBYNAME) >= GETPWBYNAME |
| && __builtin_expect (req->type, LASTDBREQ) <= LASTDBREQ) |
| || req->type == GETAI || req->type == INITGROUPS) |
| { |
| 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 (!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 (__builtin_expect (pthread_rwlock_tryrdlock (&db->lock) != 0, 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 (db->mmap_used || !cached->notfound) |
| { |
| 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 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: |
| #ifdef SCM_RIGHTS |
| send_ro_fd (serv2db[req->type], 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 open /proc/self/cmdline: %s; disabling paranoia mode"), |
| strerror (errno)); |
| |
| close (fd); |
| paranoia = 0; |
| return; |
| } |
| |
| readlen += n; |
| |
| if (readlen < buflen) |
| break; |
| |
| /* We might have to extend the buffer. */ |
| size_t old_buflen = buflen; |
| char *newp = extend_alloca (buf, buflen, 2 * buflen); |
| buf = memmove (newp, buf, old_buflen); |
| } |
| |
| close (fd); |
| |
| /* Parse the command line. Worst case scenario: every two |
| characters form one parameter (one character plus NUL). */ |
| char **argv = alloca ((readlen / 2 + 1) * sizeof (argv[0])); |
| int argc = 0; |
| |
| char *cp = buf; |
| while (cp < buf + readlen) |
| { |
| argv[argc++] = cp; |
| cp = (char *) rawmemchr (cp, '\0') + 1; |
| } |
| argv[argc] = NULL; |
| |
| /* Second, change back to the old user if we changed it. */ |
| if (server_user != NULL) |
| { |
| if (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)); |
| |
| setuid (server_uid); |
| paranoia = 0; |
| return; |
| } |
| } |
| |
| /* Next change back to the old working directory. */ |
| if (chdir (oldcwd) == -1) |
| { |
| dbg_log (_("\ |
| cannot change to old working directory: %s; disabling paranoia mode"), |
| strerror (errno)); |
| |
| if (server_user != NULL) |
| { |
| setuid (server_uid); |
| setgid (server_gid); |
| } |
| paranoia = 0; |
| return; |
| } |
| |
| /* Synchronize memory. */ |
| for (int cnt = 0; cnt < lastdb; ++cnt) |
| { |
| /* Make sure nobody keeps using the database. */ |
| dbs[cnt].head->timestamp = 0; |
| |
| if (dbs[cnt].persistent) |
| // XXX async OK? |
| msync (dbs[cnt].head, dbs[cnt].memsize, MS_ASYNC); |
| } |
| |
| /* The preparations are done. */ |
| execv ("/proc/self/exe", argv); |
| |
| /* If we come here, we will never be able to re-exec. */ |
| dbg_log (_("re-exec failed: %s; disabling paranoia mode"), |
| strerror (errno)); |
| |
| if (server_user != NULL) |
| { |
| setuid (server_uid); |
| setgid (server_gid); |
| } |
| if (chdir ("/") != 0) |
| dbg_log (_("cannot change current working directory to \"/\": %s"), |
| strerror (errno)); |
| paranoia = 0; |
| } |
| |
| |
| /* List of file descriptors. */ |
| struct fdlist |
| { |
| int fd; |
| struct fdlist *next; |
| }; |
| /* Memory allocated for the list. */ |
| static struct fdlist *fdlist; |
| /* List of currently ready-to-read file descriptors. */ |
| static struct fdlist *readylist; |
| |
| /* Conditional variable and mutex to signal availability of entries in |
| READYLIST. The condvar is initialized dynamically since we might |
| use a different clock depending on availability. */ |
| static pthread_cond_t readylist_cond; |
| static pthread_mutex_t readylist_lock = PTHREAD_MUTEX_INITIALIZER; |
| |
| /* The clock to use with the condvar. */ |
| static clockid_t timeout_clock = CLOCK_REALTIME; |
| |
| /* Number of threads ready to handle the READYLIST. */ |
| static unsigned long int nready; |
| |
| |
| /* This is the main loop. It is replicated in different threads but the |
| `poll' call makes sure only one thread handles an incoming connection. */ |
| static void * |
| __attribute__ ((__noreturn__)) |
| nscd_run (void *p) |
| { |
| const long int my_number = (long int) p; |
| const int run_prune = my_number < lastdb && dbs[my_number].enabled; |
| struct timespec prune_ts; |
| int to = 0; |
| char buf[256]; |
| |
| if (run_prune) |
| { |
| setup_thread (&dbs[my_number]); |
| |
| /* We are running. */ |
| dbs[my_number].head->timestamp = time (NULL); |
| |
| if (clock_gettime (timeout_clock, &prune_ts) == -1) |
| /* Should never happen. */ |
| abort (); |
| |
| /* Compute timeout time. */ |
| prune_ts.tv_sec += CACHE_PRUNE_INTERVAL; |
| } |
| |
| /* Initial locking. */ |
| pthread_mutex_lock (&readylist_lock); |
| |
| /* One more thread available. */ |
| ++nready; |
| |
| while (1) |
| { |
| while (readylist == NULL) |
| { |
| if (run_prune) |
| { |
| /* Wait, but not forever. */ |
| to = pthread_cond_timedwait (&readylist_cond, &readylist_lock, |
| &prune_ts); |
| |
| /* If we were woken and there is no work to be done, |
| just start pruning. */ |
| if (readylist == NULL && to == ETIMEDOUT) |
| { |
| --nready; |
| pthread_mutex_unlock (&readylist_lock); |
| goto only_prune; |
| } |
| } |
| else |
| /* No need to timeout. */ |
| pthread_cond_wait (&readylist_cond, &readylist_lock); |
| } |
| |
| 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); |
| |
| /* We do not want to block on a short read or so. */ |
| int fl = fcntl (fd, F_GETFL); |
| if (fl == -1 || fcntl (fd, F_SETFL, fl | O_NONBLOCK) == -1) |
| goto close_and_out; |
| |
| /* Now read the request. */ |
| request_header req; |
| if (__builtin_expect (TEMP_FAILURE_RETRY (read (fd, &req, sizeof (req))) |
| != sizeof (req), 0)) |
| { |
| /* We failed to read data. Note that this also might mean we |
| failed because we would have blocked. */ |
| if (debug_level > 0) |
| dbg_log (_("short read while reading request: %s"), |
| strerror_r (errno, buf, sizeof (buf))); |
| 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 (__builtin_expect (debug_level > 0, 0)) |
| { |
| struct ucred caller; |
| socklen_t optlen = sizeof (caller); |
| |
| if (getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &caller, &optlen) == 0) |
| pid = caller.pid; |
| } |
| #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. */ |
| #define MAXKEYLEN 1024 |
| 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]; |
| |
| 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; |
| } |
| |
| 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); |
| } |
| |
| close_and_out: |
| /* We are done. */ |
| close (fd); |
| |
| /* Check whether we should be pruning the cache. */ |
| assert (run_prune || to == 0); |
| if (to == ETIMEDOUT) |
| { |
| only_prune: |
| /* The pthread_cond_timedwait() call timed out. It is time |
| to clean up the cache. */ |
| assert (my_number < lastdb); |
| prune_cache (&dbs[my_number], time (NULL), -1); |
| |
| if (clock_gettime (timeout_clock, &prune_ts) == -1) |
| /* Should never happen. */ |
| abort (); |
| |
| /* Compute next timeout time. */ |
| prune_ts.tv_sec += CACHE_PRUNE_INTERVAL; |
| |
| /* In case the list is emtpy we do not want to run the prune |
| code right away again. */ |
| to = 0; |
| } |
| |
| /* Re-locking. */ |
| pthread_mutex_lock (&readylist_lock); |
| |
| /* One more thread available. */ |
| ++nready; |
| } |
| } |
| |
| |
| 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 (__builtin_expect (nready == 0, 0)) |
| { |
| ++client_queued; |
| do_signal = false; |
| |
| /* Try to start another thread to help out. */ |
| pthread_t th; |
| if (nthreads < max_nthreads |
| && pthread_create (&th, &attr, nscd_run, |
| (void *) (long int) nthreads) == 0) |
| { |
| /* We got another thread. */ |
| ++nthreads; |
| /* The new thread might 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 inline int |
| restart_p (time_t now) |
| { |
| return (paranoia && readylist == NULL && nready == nthreads |
| && now >= restart_time); |
| } |
| |
| |
| /* Array for times a connection was accepted. */ |
| static time_t *starttime; |
| |
| |
| static void |
| __attribute__ ((__noreturn__)) |
| main_loop_poll (void) |
| { |
| struct pollfd *conns = (struct pollfd *) xmalloc (nconns |
| * sizeof (conns[0])); |
| |
| conns[0].fd = sock; |
| conns[0].events = POLLRDNORM; |
| size_t nused = 1; |
| size_t firstfree = 1; |
| |
| while (1) |
| { |
| /* Wait for any event. We wait at most a couple of seconds so |
| that we can check whether we should close any of the accepted |
| connections since we have not received a request. */ |
| #define MAX_ACCEPT_TIMEOUT 30 |
| #define MIN_ACCEPT_TIMEOUT 5 |
| #define MAIN_THREAD_TIMEOUT \ |
| (MAX_ACCEPT_TIMEOUT * 1000 \ |
| - ((MAX_ACCEPT_TIMEOUT - MIN_ACCEPT_TIMEOUT) * 1000 * nused) / (2 * nconns)) |
| |
| int n = poll (conns, nused, MAIN_THREAD_TIMEOUT); |
| |
| time_t now = time (NULL); |
| |
| /* If there is a descriptor ready for reading or there is a new |
| connection, process this now. */ |
| if (n > 0) |
| { |
| if (conns[0].revents != 0) |
| { |
| /* We have a new incoming connection. Accept the connection. */ |
| int fd = TEMP_FAILURE_RETRY (accept (sock, NULL, NULL)); |
| |
| /* Use the descriptor if we have not reached the limit. */ |
| if (fd >= 0) |
| { |
| 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; |
| } |
| |
| for (size_t cnt = 1; cnt < nused && n > 0; ++cnt) |
| if (conns[cnt].revents != 0) |
| { |
| fd_ready (conns[cnt].fd); |
| |
| /* Clean up the CONNS array. */ |
| conns[cnt].fd = -1; |
| if (cnt < firstfree) |
| firstfree = cnt; |
| if (cnt == nused - 1) |
| do |
| --nused; |
| while (conns[nused - 1].fd == -1); |
| |
| --n; |
| } |
| } |
| |
| /* Now find entries which have timed out. */ |
| assert (nused > 0); |
| |
| /* We make the timeout length depend on the number of file |
| descriptors currently used. */ |
| #define ACCEPT_TIMEOUT \ |
| (MAX_ACCEPT_TIMEOUT \ |
| - ((MAX_ACCEPT_TIMEOUT - MIN_ACCEPT_TIMEOUT) * nused) / nconns) |
| time_t laststart = now - ACCEPT_TIMEOUT; |
| |
| for (size_t cnt = nused - 1; cnt > 0; --cnt) |
| { |
| if (conns[cnt].fd != -1 && starttime[cnt] < laststart) |
| { |
| /* Remove the entry, it timed out. */ |
| (void) close (conns[cnt].fd); |
| conns[cnt].fd = -1; |
| |
| if (cnt < firstfree) |
| firstfree = cnt; |
| if (cnt == nused - 1) |
| do |
| --nused; |
| while (conns[nused - 1].fd == -1); |
| } |
| } |
| |
| if (restart_p (now)) |
| restart (); |
| } |
| } |
| |
| |
| #ifdef HAVE_EPOLL |
| static void |
| main_loop_epoll (int efd) |
| { |
| struct epoll_event ev = { 0, }; |
| int nused = 1; |
| size_t highest = 0; |
| |
| /* Add the socket. */ |
| ev.events = EPOLLRDNORM; |
| ev.data.fd = sock; |
| if (epoll_ctl (efd, EPOLL_CTL_ADD, sock, &ev) == -1) |
| /* We cannot use epoll. */ |
| return; |
| |
| while (1) |
| { |
| struct epoll_event revs[100]; |
| # define nrevs (sizeof (revs) / sizeof (revs[0])) |
| |
| int n = epoll_wait (efd, revs, nrevs, MAIN_THREAD_TIMEOUT); |
| |
| time_t now = time (NULL); |
| |
| for (int cnt = 0; cnt < n; ++cnt) |
| if (revs[cnt].data.fd == sock) |
| { |
| /* A new connection. */ |
| int fd = TEMP_FAILURE_RETRY (accept (sock, NULL, NULL)); |
| |
| if (fd >= 0) |
| { |
| /* Try to add the new descriptor. */ |
| ev.data.fd = fd; |
| if (fd >= nconns |
| || epoll_ctl (efd, EPOLL_CTL_ADD, fd, &ev) == -1) |
| /* The descriptor is too large or something went |
| wrong. Close the descriptor. */ |
| close (fd); |
| else |
| { |
| /* Remember when we accepted the connection. */ |
| starttime[fd] = now; |
| |
| if (fd > highest) |
| highest = fd; |
| |
| ++nused; |
| } |
| } |
| } |
| else |
| { |
| /* Remove the descriptor from the epoll descriptor. */ |
| (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; |
| for (int cnt = highest; cnt > STDERR_FILENO; --cnt) |
| if (cnt != sock && starttime[cnt] != 0 && starttime[cnt] < laststart) |
| { |
| /* We are waiting for this one for too long. Close it. */ |
| (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 |
| |
| pthread_cond_init (&readylist_cond, &condattr); |
| pthread_condattr_destroy (&condattr); |
| |
| |
| /* Create the attribute for the threads. They are all created |
| detached. */ |
| pthread_attr_init (&attr); |
| pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); |
| /* Use 1MB stacks, twice as much for 64-bit architectures. */ |
| pthread_attr_setstacksize (&attr, 1024 * 1024 * (sizeof (void *) / 4)); |
| |
| /* We allow less than LASTDB threads only for debugging. */ |
| if (debug_level == 0) |
| nthreads = MAX (nthreads, lastdb); |
| |
| int nfailed = 0; |
| for (long int i = 0; i < nthreads; ++i) |
| { |
| pthread_t th; |
| if (pthread_create (&th, &attr, nscd_run, (void *) (i - nfailed)) != 0) |
| ++nfailed; |
| } |
| if (nthreads - nfailed < lastdb) |
| { |
| /* We could not start enough threads. */ |
| dbg_log (_("could only start %d threads; terminating"), |
| nthreads - nfailed); |
| exit (1); |
| } |
| |
| /* 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); |
| error (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); |
| error (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); |
| error (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); |
| error (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); |
| perror ("setgid"); |
| exit (4); |
| } |
| |
| 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); |
| perror ("setuid"); |
| exit (4); |
| } |
| |
| #if defined HAVE_LIBAUDIT && defined HAVE_LIBCAP |
| /* Remove the temporary capabilities. */ |
| install_real_capabilities (new_caps); |
| #endif |
| } |
| |
| /* Handle the HUP signal which will force a dump of the cache */ |
| void |
| sighup_handler (int signum) |
| { |
| /* Prune the password database. */ |
| if (dbs[pwddb].enabled) |
| prune_cache (&dbs[pwddb], LONG_MAX, -1); |
| |
| /* Prune the group database. */ |
| if (dbs[grpdb].enabled) |
| prune_cache (&dbs[grpdb], LONG_MAX, -1); |
| |
| /* Prune the host database. */ |
| if (dbs[hstdb].enabled) |
| prune_cache (&dbs[hstdb], LONG_MAX, -1); |
| } |
| |