Update.
authorUlrich Drepper <drepper@redhat.com>
Sun, 3 Oct 2004 19:33:48 +0000 (19:33 +0000)
committerUlrich Drepper <drepper@redhat.com>
Sun, 3 Oct 2004 19:33:48 +0000 (19:33 +0000)
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.

ChangeLog
nscd/connections.c
nscd/nscd.c
nscd/nscd.conf
nscd/nscd.h
nscd/nscd_conf.c
nscd/nscd_stat.c

index 258cd77..6ecb996 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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]
index 8b167aa..ace69fb 100644 (file)
@@ -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.  */
index 146f61c..0ef54bc 100644 (file)
@@ -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;
     }
 
index f972851..35f65a4 100644 (file)
@@ -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
index 3a9660d..56073eb 100644 (file)
@@ -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 */
index 2e6f812..591dea8 100644 (file)
    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.  */
index 3e3be5b..a56a381 100644 (file)
@@ -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.  */