diff options
-rw-r--r-- | ChangeLog | 20 | ||||
-rw-r--r-- | nscd/connections.c | 189 | ||||
-rw-r--r-- | nscd/nscd.c | 17 | ||||
-rw-r--r-- | nscd/nscd.conf | 4 | ||||
-rw-r--r-- | nscd/nscd.h | 17 | ||||
-rw-r--r-- | nscd/nscd_conf.c | 36 | ||||
-rw-r--r-- | nscd/nscd_stat.c | 17 |
7 files changed, 286 insertions, 14 deletions
@@ -1,5 +1,25 @@ 2004-10-03 Ulrich Drepper <drepper@redhat.com> + Implement paranoia mode. + * nscd/connections.c (nscd_init): Mark database and socket descriptors + as close on exec. + (restart): New function. + (restart_p): New function. + (nscd_run): Add missing descrement of nready in case readylist is + empty. + (main_loop_poll): Call restart_p and restart. + (main_loop_epoll): Likewise. + (begin_drop_privileges): Save original UID and GID. + * nscd/nscd.c: Define new variables paranoia, restart_time, + restart_interval, oldcwd, old_gid, old_uid. + (main): Disable paranoia mode if we are not forking. + (check_pid): When re-execing, the PID file contains the same PID as + the current process. Do not fail in this case. + * nscd/nscd.conf: Add paranoia and restart-interval entries. + * nscd/nscd.h: Define RESTART_INTERVAL. Declare new variables. + * nscd/nscd_conf.c: Parse paranoia and restart-internal configurations. + * nscd/nscd_stat.c: Print paranoia and restart-internal values. + * nscd/connections.c: Implement alternative loop for main thread which uses epoll. * sysdeps/unix/sysv/linux/Makefile [subdir=nscd] diff --git a/nscd/connections.c b/nscd/connections.c index 8b167aab71..ace69fb455 100644 --- a/nscd/connections.c +++ b/nscd/connections.c @@ -18,6 +18,7 @@ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ +#include <alloca.h> #include <assert.h> #include <atomic.h> #include <error.h> @@ -437,6 +438,18 @@ cannot create read-only descriptor for \"%s\"; no mmap"), } } + if (paranoia + && ((dbs[cnt].wr_fd != -1 + && fcntl (dbs[cnt].wr_fd, F_SETFD, FD_CLOEXEC) == -1) + || (dbs[cnt].ro_fd != -1 + && fcntl (dbs[cnt].ro_fd, F_SETFD, FD_CLOEXEC) == -1))) + { + dbg_log (_("\ +cannot set socket to close on exec: %s; disabling paranoia mode"), + strerror (errno)); + paranoia = 0; + } + if (dbs[cnt].head == NULL) { /* We do not use the persistent database. Just @@ -493,11 +506,22 @@ cannot create read-only descriptor for \"%s\"; no mmap"), exit (1); } - /* We don't wait for data otherwise races between threads can get - them stuck on accept. */ + /* We don't want to get stuck on accept. */ int fl = fcntl (sock, F_GETFL); - if (fl != -1) - fcntl (sock, F_SETFL, fl | O_NONBLOCK); + if (fl == -1 || fcntl (sock, F_SETFL, fl | O_NONBLOCK) == -1) + { + dbg_log (_("cannot change socket to nonblocking mode: %s"), + strerror (errno)); + exit (1); + } + + /* The descriptor needs to be closed on exec. */ + if (paranoia && fcntl (sock, F_SETFD, FD_CLOEXEC) == -1) + { + dbg_log (_("cannot set socket to close on exec: %s"), + strerror (errno)); + exit (1); + } /* Set permissions for the socket. */ chmod (_PATH_NSCDSOCKET, DEFFILEMODE); @@ -788,6 +812,138 @@ cannot handle old request version %d; current version is %d"), } +/* Restart the process. */ +static void +restart (void) +{ + /* First determine the parameters. We do not use the parameters + passed to main() since in case nscd is started by running the + dynamic linker this will not work. Yes, this is not the usual + case but nscd is part of glibc and we occasionally do this. */ + size_t buflen = 1024; + char *buf = alloca (buflen); + size_t readlen = 0; + int fd = open ("/proc/self/cmdline", O_RDONLY); + if (fd == -1) + { + dbg_log (_("\ +cannot open /proc/self/cmdline: %s; disabling paranoia mode"), + strerror (errno)); + + paranoia = 0; + return; + } + + while (1) + { + ssize_t n = TEMP_FAILURE_RETRY (read (fd, buf + readlen, + buflen - readlen)); + if (n == -1) + { + dbg_log (_("\ +cannot open /proc/self/cmdline: %s; disabling paranoia mode"), + strerror (errno)); + + close (fd); + paranoia = 0; + return; + } + + readlen += n; + + if (readlen < buflen) + break; + + /* We might have to extend the buffer. */ + size_t old_buflen = buflen; + char *newp = extend_alloca (buf, buflen, 2 * buflen); + buf = memmove (newp, buf, old_buflen); + } + + close (fd); + + /* Parse the command line. Worst case scenario: every two + characters form one parameter (one character plus NUL). */ + char **argv = alloca ((readlen / 2 + 1) * sizeof (argv[0])); + int argc = 0; + + char *cp = buf; + while (cp < buf + readlen) + { + argv[argc++] = cp; + cp = (char *) rawmemchr (cp, '\0') + 1; + } + argv[argc] = NULL; + + /* Second, change back to the old user if we changed it. */ + if (server_user != NULL) + { + if (setuid (old_uid) != 0) + { + dbg_log (_("\ +cannot change to old UID: %s; disabling paranoia mode"), + strerror (errno)); + + paranoia = 0; + return; + } + + if (setgid (old_gid) != 0) + { + dbg_log (_("\ +cannot change to old GID: %s; disabling paranoia mode"), + strerror (errno)); + + setuid (server_uid); + paranoia = 0; + return; + } + } + + /* Next change back to the old working directory. */ + if (chdir (oldcwd) == -1) + { + dbg_log (_("\ +cannot change to old working directory: %s; disabling paranoia mode"), + strerror (errno)); + + if (server_user != NULL) + { + setuid (server_uid); + setgid (server_gid); + } + paranoia = 0; + return; + } + + /* Synchronize memory. */ + for (int cnt = 0; cnt < lastdb; ++cnt) + { + /* Make sure nobody keeps using the database. */ + dbs[cnt].head->timestamp = 0; + + if (dbs[cnt].persistent) + // XXX async OK? + msync (dbs[cnt].head, dbs[cnt].memsize, MS_ASYNC); + } + + /* The preparations are done. */ + execv ("/proc/self/exe", argv); + + /* If we come here, we will never be able to re-exec. */ + dbg_log (_("re-exec failed: %s; disabling paranoia mode"), + strerror (errno)); + + if (server_user != NULL) + { + setuid (server_uid); + setgid (server_gid); + } + chdir ("/"); + paranoia = 0; +} + + /* List of file descriptors. */ struct fdlist { @@ -859,6 +1015,7 @@ nscd_run (void *p) just start pruning. */ if (readylist == NULL && to == ETIMEDOUT) { + --nready; pthread_mutex_unlock (&readylist_lock); goto only_prune; } @@ -1059,7 +1216,16 @@ fd_ready (int fd) } -/* Time a connection was accepted. */ +/* 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; @@ -1160,6 +1326,9 @@ main_loop_poll (void) while (conns[nused - 1].fd == -1); } } + + if (restart_p (now)) + restart (); } } @@ -1252,6 +1421,9 @@ main_loop_epoll (int efd) } else if (cnt != sock && starttime[cnt] == 0 && cnt == highest) --highest; + + if (restart_p (now)) + restart (); } } #endif @@ -1347,6 +1519,13 @@ begin_drop_privileges (void) server_uid = pwd->pw_uid; server_gid = pwd->pw_gid; + /* Save the old UID/GID if we have to change back. */ + if (paranoia) + { + old_uid = getuid (); + old_gid = getgid (); + } + if (getgrouplist (server_user, server_gid, NULL, &server_ngroups) == 0) { /* This really must never happen. */ diff --git a/nscd/nscd.c b/nscd/nscd.c index 146f61cb25..0ef54bcf08 100644 --- a/nscd/nscd.c +++ b/nscd/nscd.c @@ -79,6 +79,13 @@ time_t start_time; uintptr_t pagesize_m1; +int paranoia; +time_t restart_time; +time_t restart_interval = RESTART_INTERVAL; +const char *oldcwd; +uid_t old_uid; +gid_t old_gid; + static int check_pid (const char *file); static int write_pid (const char *file); @@ -248,6 +255,9 @@ main (int argc, char **argv) signal (SIGTTIN, SIG_IGN); signal (SIGTSTP, SIG_IGN); } + else + /* In foreground mode we are not paranoid. */ + paranoia = 0; /* Start the SELinux AVC. */ if (selinux_enabled) @@ -414,6 +424,7 @@ nscd_open_socket (void) return sock; } + /* Cleanup. */ void termination_handler (int signum) @@ -461,7 +472,11 @@ check_pid (const char *file) n = fscanf (fp, "%d", &pid); fclose (fp); - if (n != 1 || kill (pid, 0) == 0) + /* If we cannot parse the file default to assuming nscd runs. + If the PID is alive, assume it is running. That all unless + the PID is the same as the current process' since tha latter + can mean we re-exec. */ + if ((n != 1 || kill (pid, 0) == 0) && pid != getpid ()) return 1; } diff --git a/nscd/nscd.conf b/nscd/nscd.conf index f972851116..35f65a4a36 100644 --- a/nscd/nscd.conf +++ b/nscd/nscd.conf @@ -12,6 +12,8 @@ # 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> @@ -31,6 +33,8 @@ # stat-user somebody debug-level 0 # reload-count 5 + paranoia no +# restart-interval 3600 enable-cache passwd yes positive-time-to-live passwd 600 diff --git a/nscd/nscd.h b/nscd/nscd.h index 3a9660d3ec..56073ebd0c 100644 --- a/nscd/nscd.h +++ b/nscd/nscd.h @@ -50,6 +50,10 @@ typedef enum #define DEFAULT_RELOAD_LIMIT 5 +/* Time before restarting the process in paranoia mode. */ +#define RESTART_INTERVAL (60 * 60) + + /* Structure describing dynamic part of one database. */ struct database_dyn { @@ -127,6 +131,19 @@ extern unsigned int reload_count; /* Pagesize minus one. */ extern uintptr_t pagesize_m1; +/* Nonzero if paranoia mode is enabled. */ +extern int paranoia; +/* Time after which the process restarts. */ +extern time_t restart_time; +/* How much time between restarts. */ +extern time_t restart_interval; +/* Old current working directory. */ +extern const char *oldcwd; +/* Old user and group ID. */ +extern uid_t old_uid; +extern gid_t old_gid; + + /* Prototypes for global functions. */ /* nscd.c */ diff --git a/nscd/nscd_conf.c b/nscd/nscd_conf.c index 2e6f8127a7..591dea8d64 100644 --- a/nscd/nscd_conf.c +++ b/nscd/nscd_conf.c @@ -18,13 +18,15 @@ 02111-1307 USA. */ #include <ctype.h> +#include <errno.h> +#include <libintl.h> #include <malloc.h> #include <pwd.h> #include <stdio.h> #include <stdio_ext.h> #include <stdlib.h> #include <string.h> -#include <libintl.h> +#include <unistd.h> #include <sys/param.h> #include <sys/types.h> @@ -191,7 +193,7 @@ nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb]) } else if (strcmp (entry, "stat-user") == 0) { - if (!arg1) + if (arg1 == NULL) dbg_log (_("Must specify user name for stat-user option")); else { @@ -245,11 +247,41 @@ nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb]) dbg_log (_("invalid value for 'reload-count': %u"), count); } } + else if (strcmp (entry, "paranoia") == 0) + { + if (strcmp (arg1, "no") == 0) + paranoia = 0; + else if (strcmp (arg1, "yes") == 0) + paranoia = 1; + } + else if (strcmp (entry, "restart-interval") == 0) + { + if (arg1 != NULL) + restart_interval = atol (arg1); + else + dbg_log (_("Must specify value for restart-interval option")); + } else dbg_log (_("Unknown option: %s %s %s"), entry, arg1, arg2); } while (!feof_unlocked (fp)); + if (paranoia) + { + restart_time = time (NULL) + restart_interval; + + /* Save the old current workding directory if we are in paranoia + mode. We have to change back to it. */ + oldcwd = get_current_dir_name (); + if (oldcwd == NULL) + { + dbg_log (_("\ +cannot get current working directory: %s; disabling paranoia mode"), + strerror (errno)); + paranoia = 0; + } + } + /* Free the buffer. */ free (line); /* Close configuration file. */ diff --git a/nscd/nscd_stat.c b/nscd/nscd_stat.c index 3e3be5bc8c..a56a381b25 100644 --- a/nscd/nscd_stat.c +++ b/nscd/nscd_stat.c @@ -143,6 +143,8 @@ receive_print_stats (void) int fd; int i; uid_t uid = getuid (); + const char *yesstr = _(" yes"); + const char *nostr = _(" no"); /* Find out whether there is another user but root allowed to request statistics. */ @@ -223,8 +225,11 @@ receive_print_stats (void) else printf (_(" %2lus server runtime\n"), diff); - printf (_("%15lu number of times clients had to wait\n"), - data.client_queued); + printf (_("%15lu number of times clients had to wait\n" + "%15s paranoia mode enabled\n" + "%15lu restart internal\n"), + data.client_queued, paranoia ? yesstr : nostr, + (unsigned long int) restart_interval); for (i = 0; i < lastdb; ++i) { @@ -241,13 +246,13 @@ receive_print_stats (void) /* The locale does not provide this information so we have to translate it ourself. Since we should avoid short translation terms we artifically increase the length. */ - enabled = data.dbs[i].enabled ? _(" yes") : _(" no"); + enabled = data.dbs[i].enabled ? yesstr : nostr; if (check_file[0] == '\0') - check_file = data.dbs[i].check_file ? _(" yes") : _(" no"); + check_file = data.dbs[i].check_file ? yesstr : nostr; if (shared[0] == '\0') - shared = data.dbs[i].shared ? _(" yes") : _(" no"); + shared = data.dbs[i].shared ? yesstr : nostr; if (persistent[0] == '\0') - persistent = data.dbs[i].persistent ? _(" yes") : _(" no"); + persistent = data.dbs[i].persistent ? yesstr : nostr; if (all == 0) /* If nothing happened so far report a 0% hit rate. */ |