bus: Raise file descriptor limit to match configuration
[platform/upstream/dbus.git] / dbus / dbus-sysdeps-util-unix.c
index df967a3..02954d5 100644 (file)
  * 
  * 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
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
+
+#include <config.h>
 #include "dbus-sysdeps.h"
 #include "dbus-sysdeps-unix.h"
 #include "dbus-internals.h"
+#include "dbus-pipe.h"
 #include "dbus-protocol.h"
 #include "dbus-string.h"
 #define DBUS_USERDB_INCLUDES_PRIVATE 1
 #include <errno.h>
 #include <fcntl.h>
 #include <sys/stat.h>
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
 #include <grp.h>
 #include <sys/socket.h>
 #include <dirent.h>
 #include <sys/un.h>
-#ifdef HAVE_LIBAUDIT
-#include <sys/prctl.h>
-#include <sys/capability.h>
-#include <libaudit.h>
-#endif /* HAVE_LIBAUDIT */
+#include <syslog.h>
 
 #ifdef HAVE_SYS_SYSLIMITS_H
 #include <sys/syslimits.h>
  * @param pidfile #NULL, or pidfile to create
  * @param print_pid_pipe pipe to print daemon's pid to, or -1 for none
  * @param error return location for errors
+ * @param keep_umask #TRUE to keep the original umask
  * @returns #FALSE on failure
  */
 dbus_bool_t
 _dbus_become_daemon (const DBusString *pidfile,
                      DBusPipe         *print_pid_pipe,
-                     DBusError        *error)
+                     DBusError        *error,
+                     dbus_bool_t       keep_umask)
 {
   const char *s;
   pid_t child_pid;
@@ -121,9 +125,12 @@ _dbus_become_daemon (const DBusString *pidfile,
             _dbus_verbose ("keeping stderr open due to DBUS_DEBUG_OUTPUT\n");
         }
 
-      /* Get a predictable umask */
-      _dbus_verbose ("setting umask\n");
-      umask (022);
+      if (!keep_umask)
+        {
+          /* Get a predictable umask */
+          _dbus_verbose ("setting umask\n");
+          umask (022);
+        }
 
       _dbus_verbose ("calling setsid()\n");
       if (setsid () == -1)
@@ -247,7 +254,8 @@ _dbus_write_pid_to_file_and_pipe (const DBusString *pidfile,
       DBusString pid;
       int bytes;
 
-      _dbus_verbose ("writing our pid to pipe %d\n", print_pid_pipe->fd_or_handle);
+      _dbus_verbose ("writing our pid to pipe %"PRIuPTR"\n",
+                     print_pid_pipe->fd_or_handle);
       
       if (!_dbus_string_init (&pid))
         {
@@ -302,6 +310,9 @@ _dbus_verify_daemon_user (const char *user)
   return _dbus_get_user_id_and_primary_group (&u, NULL, NULL);
 }
 
+
+/* The HAVE_LIBAUDIT case lives in selinux.c */
+#ifndef HAVE_LIBAUDIT
 /**
  * Changes the user and group the bus is running as.
  *
@@ -316,13 +327,9 @@ _dbus_change_to_daemon_user  (const char    *user,
   dbus_uid_t uid;
   dbus_gid_t gid;
   DBusString u;
-#ifdef HAVE_LIBAUDIT
-  dbus_bool_t we_were_root;
-  cap_t new_caps;
-#endif
-  
+
   _dbus_string_init_const (&u, user);
-  
+
   if (!_dbus_get_user_id_and_primary_group (&u, &uid, &gid))
     {
       dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -330,59 +337,7 @@ _dbus_change_to_daemon_user  (const char    *user,
                       user);
       return FALSE;
     }
-  
-#ifdef HAVE_LIBAUDIT
-  we_were_root = _dbus_getuid () == 0;
-  new_caps = NULL;
-  /* have a tmp set of caps that we use to transition to the usr/grp dbus should
-   * run as ... doesn't really help. But keeps people happy.
-   */
-    
-  if (!we_were_root)
-    {
-      cap_value_t new_cap_list[] = { CAP_AUDIT_WRITE };
-      cap_value_t tmp_cap_list[] = { CAP_AUDIT_WRITE, CAP_SETUID, CAP_SETGID };
-      cap_t tmp_caps = cap_init();
-        
-      if (!tmp_caps || !(new_caps = cap_init ()))
-        {
-          dbus_set_error (error, DBUS_ERROR_FAILED,
-                          "Failed to initialize drop of capabilities: %s\n",
-                          _dbus_strerror (errno));
-
-          if (tmp_caps)
-            cap_free (tmp_caps);
 
-          return FALSE;
-        }
-
-      /* assume these work... */
-      cap_set_flag (new_caps, CAP_PERMITTED, 1, new_cap_list, CAP_SET);
-      cap_set_flag (new_caps, CAP_EFFECTIVE, 1, new_cap_list, CAP_SET);
-      cap_set_flag (tmp_caps, CAP_PERMITTED, 3, tmp_cap_list, CAP_SET);
-      cap_set_flag (tmp_caps, CAP_EFFECTIVE, 3, tmp_cap_list, CAP_SET);
-      
-      if (prctl (PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1)
-        {
-          dbus_set_error (error, _dbus_error_from_errno (errno),
-                          "Failed to set keep-capabilities: %s\n",
-                          _dbus_strerror (errno));
-          cap_free (tmp_caps);
-          goto fail;
-        }
-        
-      if (cap_set_proc (tmp_caps) == -1)
-        {
-          dbus_set_error (error, DBUS_ERROR_FAILED,
-                          "Failed to drop capabilities: %s\n",
-                          _dbus_strerror (errno));
-          cap_free (tmp_caps);
-          goto fail;
-        }
-      cap_free (tmp_caps);
-    }
-#endif /* HAVE_LIBAUDIT */
-  
   /* setgroups() only works if we are a privileged process,
    * so we don't return error on failure; the only possible
    * failure is that we don't have perms to do it.
@@ -393,7 +348,7 @@ _dbus_change_to_daemon_user  (const char    *user,
   if (setgroups (0, NULL) < 0)
     _dbus_warn ("Failed to drop supplementary groups: %s\n",
                 _dbus_strerror (errno));
-  
+
   /* Set GID first, or the setuid may remove our permission
    * to change the GID
    */
@@ -402,53 +357,129 @@ _dbus_change_to_daemon_user  (const char    *user,
       dbus_set_error (error, _dbus_error_from_errno (errno),
                       "Failed to set GID to %lu: %s", gid,
                       _dbus_strerror (errno));
-      goto fail;
+      return FALSE;
     }
-  
+
   if (setuid (uid) < 0)
     {
       dbus_set_error (error, _dbus_error_from_errno (errno),
                       "Failed to set UID to %lu: %s", uid,
                       _dbus_strerror (errno));
-      goto fail;
+      return FALSE;
     }
-  
-#ifdef HAVE_LIBAUDIT
-  if (!we_were_root)
-    {
-      if (cap_set_proc (new_caps))
-        {
-          dbus_set_error (error, DBUS_ERROR_FAILED,
-                          "Failed to drop capabilities: %s\n",
-                          _dbus_strerror (errno));
-          goto fail;
-        }
-      cap_free (new_caps);
 
-      /* should always work, if it did above */      
-      if (prctl (PR_SET_KEEPCAPS, 0, 0, 0, 0) == -1)
-        {
-          dbus_set_error (error, _dbus_error_from_errno (errno),
-                          "Failed to unset keep-capabilities: %s\n",
-                          _dbus_strerror (errno));
-          return FALSE;
-        }
-    }
-#endif
+  return TRUE;
+}
+#endif /* !HAVE_LIBAUDIT */
+
+
+/**
+ * Attempt to ensure that the current process can open
+ * at least @limit file descriptors.
+ *
+ * If @limit is lower than the current, it will not be
+ * lowered.  No error is returned if the request can
+ * not be satisfied.
+ *
+ * @limit Number of file descriptors
+ */
+void
+_dbus_request_file_descriptor_limit (unsigned int limit)
+{
+#ifdef HAVE_SETRLIMIT
+  struct rlimit lim;
+  struct rlimit target_lim;
+  unsigned int current_limit;
+
+  /* No point to doing this practically speaking
+   * if we're not uid 0.  We expect the system
+   * bus to use this before we change UID, and
+   * the session bus takes the Linux default
+   * of 1024 for both cur and max.
+   */
+  if (getuid () != 0)
+    return;
+
+  if (getrlimit (RLIMIT_NOFILE, &lim) < 0)
+    return;
 
- return TRUE;
+  if (lim.rlim_cur >= limit)
+    return;
 
- fail:
-#ifdef HAVE_LIBAUDIT
- if (!we_were_root)
-   {
-     /* should always work, if it did above */
-     prctl (PR_SET_KEEPCAPS, 0, 0, 0, 0);
-     cap_free (new_caps);
-   }
+  /* Ignore "maximum limit", assume we have the "superuser"
+   * privileges.  On Linux this is CAP_SYS_RESOURCE.
+   */
+  target_lim.rlim_cur = target_lim.rlim_max = limit;
+  /* Also ignore errors; if we fail, we will at least work
+   * up to whatever limit we had, which seems better than
+   * just outright aborting.
+   *
+   * However, in the future we should probably log this so OS builders
+   * have a chance to notice any misconfiguration like dbus-daemon
+   * being started without CAP_SYS_RESOURCE.
+   */
+  setrlimit (RLIMIT_NOFILE, &target_lim);
 #endif
+}
+
+void 
+_dbus_init_system_log (void)
+{
+  openlog ("dbus", LOG_PID, LOG_DAEMON);
+}
+/**
+ * Log a message to the system log file (e.g. syslog on Unix).
+ *
+ * @param severity a severity value
+ * @param msg a printf-style format string
+ * @param args arguments for the format string
+ *
+ */
+void
+_dbus_system_log (DBusSystemLogSeverity severity, const char *msg, ...)
+{
+  va_list args;
+
+  va_start (args, msg);
+
+  _dbus_system_logv (severity, msg, args);
+
+  va_end (args);
+}
+
+/**
+ * Log a message to the system log file (e.g. syslog on Unix).
+ *
+ * @param severity a severity value
+ * @param msg a printf-style format string
+ * @param args arguments for the format string
+ *
+ * If the FATAL severity is given, this function will terminate the program
+ * with an error code.
+ */
+void
+_dbus_system_logv (DBusSystemLogSeverity severity, const char *msg, va_list args)
+{
+  int flags;
+  switch (severity)
+    {
+      case DBUS_SYSTEM_LOG_INFO:
+        flags =  LOG_DAEMON | LOG_NOTICE;
+        break;
+      case DBUS_SYSTEM_LOG_SECURITY:
+        flags = LOG_AUTH | LOG_NOTICE;
+        break;
+      case DBUS_SYSTEM_LOG_FATAL:
+        flags = LOG_DAEMON|LOG_CRIT;
+        break;
+      default:
+        return;
+    }
+
+  vsyslog (flags, msg, args);
 
- return FALSE;
+  if (severity == DBUS_SYSTEM_LOG_FATAL)
+    exit (1);
 }
 
 /** Installs a UNIX signal handler
@@ -470,35 +501,6 @@ _dbus_set_signal_handler (int               sig,
   sigaction (sig,  &act, NULL);
 }
 
-
-/**
- * Removes a directory; Directory must be empty
- * 
- * @param filename directory filename
- * @param error initialized error object
- * @returns #TRUE on success
- */
-dbus_bool_t
-_dbus_delete_directory (const DBusString *filename,
-                       DBusError        *error)
-{
-  const char *filename_c;
-  
-  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
-
-  filename_c = _dbus_string_get_const_data (filename);
-
-  if (rmdir (filename_c) != 0)
-    {
-      dbus_set_error (error, DBUS_ERROR_FAILED,
-                     "Failed to remove directory %s: %s\n",
-                     filename_c, _dbus_strerror (errno));
-      return FALSE;
-    }
-  
-  return TRUE;
-}
-
 /** Checks if a file exists
 *
 * @param file full path to the file
@@ -659,54 +661,14 @@ _dbus_directory_open (const DBusString *filename,
   return iter;
 }
 
-/* Calculate the required buffer size (in bytes) for directory
- * entries read from the given directory handle.  Return -1 if this
- * this cannot be done. 
- *
- * If you use autoconf, include fpathconf and dirfd in your
- * AC_CHECK_FUNCS list.  Otherwise use some other method to detect
- * and use them where available.
- */
-static dbus_bool_t
-dirent_buf_size(DIR * dirp, size_t *size)
-{
- long name_max;
-#   if defined(HAVE_FPATHCONF) && defined(_PC_NAME_MAX)
-#      if defined(HAVE_DIRFD)
-          name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
-#      elif defined(HAVE_DDFD)
-          name_max = fpathconf(dirp->dd_fd, _PC_NAME_MAX);
-#      else
-          name_max = fpathconf(dirp->__dd_fd, _PC_NAME_MAX);
-#      endif /* HAVE_DIRFD */
-     if (name_max == -1)
-#           if defined(NAME_MAX)
-            name_max = NAME_MAX;
-#           else
-            return FALSE;
-#           endif
-#   elif defined(MAXNAMELEN)
-     name_max = MAXNAMELEN;
-#   else
-#       if defined(NAME_MAX)
-        name_max = NAME_MAX;
-#       else
-#           error "buffer size for readdir_r cannot be determined"
-#       endif
-#   endif
-  if (size)
-    *size = (size_t)offsetof(struct dirent, d_name) + name_max + 1;
-  else
-    return FALSE;
-
-  return TRUE;
-}
-
 /**
  * Get next file in the directory. Will not return "." or ".."  on
  * UNIX. If an error occurs, the contents of "filename" are
  * undefined. The error is never set if the function succeeds.
  *
+ * This function is not re-entrant, and not necessarily thread-safe.
+ * Only use it for test code or single-threaded utilities.
+ *
  * @param iter the iterator
  * @param filename string to be set to the next file in the dir
  * @param error return location for error
@@ -717,37 +679,24 @@ _dbus_directory_get_next_file (DBusDirIter      *iter,
                                DBusString       *filename,
                                DBusError        *error)
 {
-  struct dirent *d, *ent;
-  size_t buf_size;
+  struct dirent *ent;
   int err;
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
-  if (!dirent_buf_size (iter->d, &buf_size))
-    {
-      dbus_set_error (error, DBUS_ERROR_FAILED,
-                      "Can't calculate buffer size when reading directory");
-      return FALSE;
-    }
-
-  d = (struct dirent *)dbus_malloc (buf_size);
-  if (!d)
-    {
-      dbus_set_error (error, DBUS_ERROR_NO_MEMORY,
-                      "No memory to read directory entry");
-      return FALSE;
-    }
 
  again:
-  err = readdir_r (iter->d, d, &ent);
-  if (err || !ent)
+  errno = 0;
+  ent = readdir (iter->d);
+
+  if (!ent)
     {
+      err = errno;
+
       if (err != 0)
         dbus_set_error (error,
                         _dbus_error_from_errno (err),
                         "%s", _dbus_strerror (err));
 
-      dbus_free (d);
       return FALSE;
     }
   else if (ent->d_name[0] == '.' &&
@@ -761,12 +710,10 @@ _dbus_directory_get_next_file (DBusDirIter      *iter,
         {
           dbus_set_error (error, DBUS_ERROR_NO_MEMORY,
                           "No memory to read directory entry");
-          dbus_free (d);
           return FALSE;
         }
       else
         {
-          dbus_free (d);
           return TRUE;
         }
     }
@@ -828,31 +775,69 @@ fill_group_info (DBusGroupInfo    *info,
   {
     struct group *g;
     int result;
-    char buf[1024];
+    size_t buflen;
+    char *buf;
     struct group g_str;
+    dbus_bool_t b;
 
-    g = NULL;
-#ifdef HAVE_POSIX_GETPWNAM_R
+    /* retrieve maximum needed size for buf */
+    buflen = sysconf (_SC_GETGR_R_SIZE_MAX);
 
-    if (group_c_str)
-      result = getgrnam_r (group_c_str, &g_str, buf, sizeof (buf),
-                           &g);
-    else
-      result = getgrgid_r (gid, &g_str, buf, sizeof (buf),
-                           &g);
+    /* sysconf actually returns a long, but everything else expects size_t,
+     * so just recast here.
+     * https://bugs.freedesktop.org/show_bug.cgi?id=17061
+     */
+    if ((long) buflen <= 0)
+      buflen = 1024;
+
+    result = -1;
+    while (1)
+      {
+        buf = dbus_malloc (buflen);
+        if (buf == NULL)
+          {
+            dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+            return FALSE;
+          }
+
+        g = NULL;
+#ifdef HAVE_POSIX_GETPWNAM_R
+        if (group_c_str)
+          result = getgrnam_r (group_c_str, &g_str, buf, buflen,
+                               &g);
+        else
+          result = getgrgid_r (gid, &g_str, buf, buflen,
+                               &g);
 #else
-    g = getgrnam_r (group_c_str, &g_str, buf, sizeof (buf));
-    result = 0;
+        g = getgrnam_r (group_c_str, &g_str, buf, buflen);
+        result = 0;
 #endif /* !HAVE_POSIX_GETPWNAM_R */
+        /* Try a bigger buffer if ERANGE was returned:
+           https://bugs.freedesktop.org/show_bug.cgi?id=16727
+        */
+        if (result == ERANGE && buflen < 512 * 1024)
+          {
+            dbus_free (buf);
+            buflen *= 2;
+          }
+        else
+          {
+            break;
+          }
+      }
+
     if (result == 0 && g == &g_str)
       {
-        return fill_user_info_from_group (g, info, error);
+        b = fill_user_info_from_group (g, info, error);
+        dbus_free (buf);
+        return b;
       }
     else
       {
         dbus_set_error (error, _dbus_error_from_errno (errno),
                         "Group %s unknown or failed to look it up\n",
                         group_c_str ? group_c_str : "???");
+        dbus_free (buf);
         return FALSE;
       }
   }
@@ -990,7 +975,7 @@ _dbus_unix_user_is_at_console (dbus_uid_t         uid,
 dbus_bool_t
 _dbus_unix_user_is_process_owner (dbus_uid_t uid)
 {
-  return uid == _dbus_getuid ();
+  return uid == _dbus_geteuid ();
 }
 
 /**
@@ -1062,3 +1047,102 @@ _dbus_string_get_dirname  (const DBusString *filename,
 }
 /** @} */ /* DBusString stuff */
 
+static void
+string_squash_nonprintable (DBusString *str)
+{
+  unsigned char *buf;
+  int i, len; 
+  
+  buf = _dbus_string_get_data (str);
+  len = _dbus_string_get_length (str);
+  
+  for (i = 0; i < len; i++)
+    {
+         unsigned char c = (unsigned char) buf[i];
+      if (c == '\0')
+        c = ' ';
+      else if (c < 0x20 || c > 127)
+        c = '?';
+    }
+}
+
+/**
+ * Get a printable string describing the command used to execute
+ * the process with pid.  This string should only be used for
+ * informative purposes such as logging; it may not be trusted.
+ * 
+ * The command is guaranteed to be printable ASCII and no longer
+ * than max_len.
+ * 
+ * @param pid Process id
+ * @param str Append command to this string
+ * @param max_len Maximum length of returned command
+ * @param error return location for errors
+ * @returns #FALSE on error
+ */
+dbus_bool_t 
+_dbus_command_for_pid (unsigned long  pid,
+                       DBusString    *str,
+                       int            max_len,
+                       DBusError     *error)
+{
+  /* This is all Linux-specific for now */
+  DBusString path;
+  DBusString cmdline;
+  int fd;
+  
+  if (!_dbus_string_init (&path)) 
+    {
+      _DBUS_SET_OOM (error);
+      return FALSE;
+    }
+  
+  if (!_dbus_string_init (&cmdline))
+    {
+      _DBUS_SET_OOM (error);
+      _dbus_string_free (&path);
+      return FALSE;
+    }
+  
+  if (!_dbus_string_append_printf (&path, "/proc/%ld/cmdline", pid))
+    goto oom;
+  
+  fd = open (_dbus_string_get_const_data (&path), O_RDONLY);
+  if (fd < 0) 
+    {
+      dbus_set_error (error,
+                      _dbus_error_from_errno (errno),
+                      "Failed to open \"%s\": %s",
+                      _dbus_string_get_const_data (&path),
+                      _dbus_strerror (errno));
+      goto fail;
+    }
+  
+  if (!_dbus_read (fd, &cmdline, max_len))
+    {
+      dbus_set_error (error,
+                      _dbus_error_from_errno (errno),
+                      "Failed to read from \"%s\": %s",
+                      _dbus_string_get_const_data (&path),
+                      _dbus_strerror (errno));      
+      goto fail;
+    }
+  
+  if (!_dbus_close (fd, error))
+    goto fail;
+  
+  string_squash_nonprintable (&cmdline);  
+  
+  if (!_dbus_string_copy (&cmdline, 0, str, _dbus_string_get_length (str)))
+    goto oom;
+  
+  _dbus_string_free (&cmdline);  
+  _dbus_string_free (&path);
+  return TRUE;
+oom:
+  _DBUS_SET_OOM (error);
+fail:
+  _dbus_string_free (&cmdline);
+  _dbus_string_free (&path);
+  return FALSE;
+}