Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
glibc/nscd/connections.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
444 lines (376 sloc)
10.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Inner loops of cache daemon. | |
Copyright (C) 1998, 1999 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 Library General Public License as | |
published by the Free Software Foundation; either version 2 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 | |
Library General Public License for more details. | |
You should have received a copy of the GNU Library General Public | |
License along with the GNU C Library; see the file COPYING.LIB. If not, | |
write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
Boston, MA 02111-1307, USA. */ | |
#include <assert.h> | |
#include <error.h> | |
#include <errno.h> | |
#include <pthread.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <arpa/inet.h> | |
#include <sys/param.h> | |
#include <sys/poll.h> | |
#include <sys/socket.h> | |
#include <sys/stat.h> | |
#include <sys/un.h> | |
#include "nscd.h" | |
#include "dbg_log.h" | |
/* Mapping of request type to database. */ | |
static const dbtype serv2db[LASTDBREQ + 1] = | |
{ | |
[GETPWBYNAME] = pwddb, | |
[GETPWBYUID] = pwddb, | |
[GETGRBYNAME] = grpdb, | |
[GETGRBYGID] = grpdb, | |
[GETHOSTBYNAME] = hstdb, | |
[GETHOSTBYNAMEv6] = hstdb, | |
[GETHOSTBYADDR] = hstdb, | |
[GETHOSTBYADDRv6] = hstdb, | |
}; | |
/* 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" | |
}; | |
/* The control data structures for the services. */ | |
static struct database dbs[lastdb] = | |
{ | |
[pwddb] = { | |
lock: PTHREAD_RWLOCK_INITIALIZER, | |
enabled: 0, | |
check_file: 1, | |
filename: "/etc/passwd", | |
module: 211, | |
disabled_iov: &pwd_iov_disabled, | |
postimeout: 3600, | |
negtimeout: 20 | |
}, | |
[grpdb] = { | |
lock: PTHREAD_RWLOCK_INITIALIZER, | |
enabled: 0, | |
check_file: 1, | |
filename: "/etc/group", | |
module: 211, | |
disabled_iov: &grp_iov_disabled, | |
postimeout: 3600, | |
negtimeout: 60 | |
}, | |
[hstdb] = { | |
lock: PTHREAD_RWLOCK_INITIALIZER, | |
enabled: 0, | |
check_file: 1, | |
filename: "/etc/hosts", | |
module: 211, | |
disabled_iov: &hst_iov_disabled, | |
postimeout: 3600, | |
negtimeout: 20 | |
} | |
}; | |
/* Number of seconds between two cache pruning runs. */ | |
#define CACHE_PRUNE_INTERVAL 15 | |
/* Number of threads to use. */ | |
int nthreads = -1; | |
/* Socket for incoming connections. */ | |
static int sock; | |
/* Initialize database information structures. */ | |
void | |
nscd_init (const char *conffile) | |
{ | |
struct sockaddr_un sock_addr; | |
size_t cnt; | |
/* Read the configuration file. */ | |
if (nscd_parse_file (conffile, dbs) != 0) | |
{ | |
/* We couldn't read the configuration file. Disable all services | |
by shutting down the srever. */ | |
dbg_log (_("cannot read configuration file; this is fatal")); | |
exit (1); | |
} | |
if (nthreads == -1) | |
/* No configuration for this value, assume a default. */ | |
nthreads = 2 * lastdb; | |
for (cnt = 0; cnt < lastdb; ++cnt) | |
if (dbs[cnt].enabled) | |
{ | |
pthread_rwlock_init (&dbs[cnt].lock, NULL); | |
dbs[cnt].array = (struct hashentry **) | |
calloc (dbs[cnt].module, sizeof (struct hashentry *)); | |
if (dbs[cnt].array == NULL) | |
error (EXIT_FAILURE, errno, "while allocating cache"); | |
if (dbs[cnt].check_file) | |
{ | |
/* We need the modification date of the file. */ | |
struct stat st; | |
if (stat (dbs[cnt].filename, &st) < 0) | |
{ | |
char buf[128]; | |
/* We cannot stat() the file, disable file checking. */ | |
dbg_log (_("cannot stat() file `%s': %s"), | |
dbs[cnt].filename, | |
strerror_r (errno, buf, sizeof (buf))); | |
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 (1); | |
} | |
/* Bind a name to the socket. */ | |
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 (1); | |
} | |
/* Set permissions for the socket. */ | |
chmod (_PATH_NSCDSOCKET, 0666); | |
/* 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); | |
} | |
} | |
/* Close the connections. */ | |
void | |
close_sockets (void) | |
{ | |
close (sock); | |
} | |
/* Handle new request. */ | |
static void | |
handle_request (int fd, request_header *req, void *key) | |
{ | |
if (debug_level > 0) | |
dbg_log (_("handle_request: request received (Version = %d)"), | |
req->version); | |
if (req->version != NSCD_VERSION) | |
{ | |
dbg_log (_("\ | |
cannot handle old request version %d; current version is %d"), | |
req->version, NSCD_VERSION); | |
return; | |
} | |
if (req->type >= GETPWBYNAME && req->type <= LASTDBREQ) | |
{ | |
struct hashentry *cached; | |
struct database *db = &dbs[serv2db[req->type]]; | |
if (debug_level > 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], key); | |
} | |
/* Is this service enabled? */ | |
if (!db->enabled) | |
{ | |
/* No, sent the prepared record. */ | |
if (TEMP_FAILURE_RETRY (write (fd, db->disabled_iov->iov_base, | |
db->disabled_iov->iov_len)) | |
!= db->disabled_iov->iov_len) | |
{ | |
/* 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. */ | |
pthread_rwlock_rdlock (&db->lock); | |
/* See whether we can handle it from the cache. */ | |
cached = (struct hashentry *) cache_search (req->type, key, req->key_len, | |
db); | |
if (cached != NULL) | |
{ | |
/* Hurray it's in the cache. */ | |
if (TEMP_FAILURE_RETRY (write (fd, cached->packet, cached->total)) | |
!= cached->total) | |
{ | |
/* 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 (debug_level > 0) | |
dbg_log ("\t%s", serv2str[req->type]); | |
/* Handle the request. */ | |
switch (req->type) | |
{ | |
case GETPWBYNAME: | |
addpwbyname (&dbs[serv2db[req->type]], fd, req, key); | |
break; | |
case GETPWBYUID: | |
addpwbyuid (&dbs[serv2db[req->type]], fd, req, key); | |
break; | |
case GETGRBYNAME: | |
addgrbyname (&dbs[serv2db[req->type]], fd, req, key); | |
break; | |
case GETGRBYGID: | |
addgrbygid (&dbs[serv2db[req->type]], fd, req, key); | |
break; | |
case GETHOSTBYNAME: | |
addhstbyname (&dbs[serv2db[req->type]], fd, req, key); | |
break; | |
case GETHOSTBYNAMEv6: | |
addhstbynamev6 (&dbs[serv2db[req->type]], fd, req, key); | |
break; | |
case GETHOSTBYADDR: | |
addhstbyaddr (&dbs[serv2db[req->type]], fd, req, key); | |
break; | |
case GETHOSTBYADDRv6: | |
addhstbyaddrv6 (&dbs[serv2db[req->type]], fd, req, key); | |
break; | |
case GETSTAT: | |
send_stats (fd, dbs); | |
break; | |
case SHUTDOWN: | |
termination_handler (0); | |
break; | |
default: | |
abort (); | |
} | |
} | |
/* 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) | |
{ | |
int my_number = (int) p; | |
struct pollfd conn; | |
int run_prune = my_number < lastdb && dbs[my_number].enabled; | |
time_t now = time (NULL); | |
time_t next_prune = now + CACHE_PRUNE_INTERVAL; | |
int timeout = run_prune ? 1000 * (next_prune - now) : -1; | |
conn.fd = sock; | |
conn.events = POLLRDNORM; | |
while (1) | |
{ | |
int nr = poll (&conn, 1, timeout); | |
if (nr == 0) | |
{ | |
/* The `poll' call timed out. It's time to clean up the cache. */ | |
assert (my_number < lastdb); | |
now = time (NULL); | |
prune_cache (&dbs[my_number], now); | |
next_prune = now + CACHE_PRUNE_INTERVAL; | |
timeout = 1000 * (next_prune - now); | |
continue; | |
} | |
/* We have a new incoming connection. */ | |
if (conn.revents & (POLLRDNORM|POLLERR|POLLHUP|POLLNVAL)) | |
{ | |
/* Accept the connection. */ | |
int fd = accept (conn.fd, NULL, NULL); | |
request_header req; | |
char buf[256]; | |
if (fd < 0) | |
{ | |
dbg_log (_("while accepting connection: %s"), | |
strerror_r (errno, buf, sizeof (buf))); | |
continue; | |
} | |
/* Now read the request. */ | |
if (TEMP_FAILURE_RETRY (read (fd, &req, sizeof (req))) | |
!= sizeof (req)) | |
{ | |
dbg_log (_("short read while reading request: %s"), | |
strerror_r (errno, buf, sizeof (buf))); | |
close (fd); | |
continue; | |
} | |
/* 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 (req.key_len < 0 || req.key_len > 1024) | |
{ | |
dbg_log (_("key length in request too long: %Zd"), req.key_len); | |
close (fd); | |
continue; | |
} | |
else | |
{ | |
/* Get the key. */ | |
char keybuf[req.key_len]; | |
if (TEMP_FAILURE_RETRY (read (fd, keybuf, req.key_len)) | |
!= req.key_len) | |
{ | |
dbg_log (_("short read while reading request key: %s"), | |
strerror_r (errno, buf, sizeof (buf))); | |
close (fd); | |
continue; | |
} | |
/* Phew, we got all the data, now process it. */ | |
handle_request (fd, &req, keybuf); | |
/* We are done. */ | |
close (fd); | |
} | |
} | |
if (run_prune) | |
{ | |
now = time (NULL); | |
timeout = now < next_prune ? 1000 * (next_prune - now) : 0; | |
} | |
} | |
} | |
/* Start all the threads we want. The initial process is thread no. 1. */ | |
void | |
start_threads (void) | |
{ | |
int i; | |
pthread_attr_t attr; | |
pthread_t th; | |
pthread_attr_init (&attr); | |
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); | |
/* We allow less than LASTDB threads only for debugging. */ | |
if (debug_level == 0) | |
nthreads = MAX (nthreads, lastdb); | |
for (i = 1; i < nthreads; ++i) | |
pthread_create (&th, &attr, nscd_run, (void *) i); | |
pthread_attr_destroy (&attr); | |
nscd_run ((void *) 0); | |
} |