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.
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]
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>
}
}
+ 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
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);
}
+/* 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
{
just start pruning. */
if (readylist == NULL && to == ETIMEDOUT)
{
+ --nready;
pthread_mutex_unlock (&readylist_lock);
goto only_prune;
}
}
-/* 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;
while (conns[nused - 1].fd == -1);
}
}
+
+ if (restart_p (now))
+ restart ();
}
}
}
else if (cnt != sock && starttime[cnt] == 0 && cnt == highest)
--highest;
+
+ if (restart_p (now))
+ restart ();
}
}
#endif
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. */
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);
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)
return sock;
}
+
/* Cleanup. */
void
termination_handler (int signum)
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;
}
# 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>
# stat-user somebody
debug-level 0
# reload-count 5
+ paranoia no
+# restart-interval 3600
enable-cache passwd yes
positive-time-to-live passwd 600
#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
{
/* 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 */
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>
}
else if (strcmp (entry, "stat-user") == 0)
{
- if (!arg1)
+ if (arg1 == NULL)
dbg_log (_("Must specify user name for stat-user option"));
else
{
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. */
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. */
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)
{
/* 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. */