2007-07-24 Richard Hughes <richard@hughsie.com>
authorRichard Hughes <richard@hughsie.com>
Tue, 24 Jul 2007 12:01:32 +0000 (12:01 +0000)
committerRichard Hughes <richard@hughsie.com>
Tue, 24 Jul 2007 12:01:32 +0000 (12:01 +0000)
* bus/activation-helper-bin.c: (convert_error_to_exit_code),
(main):
* bus/activation-helper.c: (desktop_file_for_name),
(clear_environment), (check_permissions), (check_service_name),
(get_parameters_for_service), (switch_user),
(exec_for_correct_user), (check_bus_name), (get_correct_parser),
(launch_bus_name), (check_dbus_user), (run_launch_helper):
* bus/activation-helper.h:
Add the initial launch-helper. This is split into a main section and a
binary loader that allows us to lauch the main section in another test
harness to do stuff like OOM testing. No build glue yet.

ChangeLog
bus/activation-helper-bin.c [new file with mode: 0644]
bus/activation-helper.c [new file with mode: 0644]
bus/activation-helper.h [new file with mode: 0644]

index 8519708..55b8818 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2007-07-24  Richard Hughes  <richard@hughsie.com>
 
+       * bus/activation-helper-bin.c: (convert_error_to_exit_code),
+       (main):
+       * bus/activation-helper.c: (desktop_file_for_name),
+       (clear_environment), (check_permissions), (check_service_name),
+       (get_parameters_for_service), (switch_user),
+       (exec_for_correct_user), (check_bus_name), (get_correct_parser),
+       (launch_bus_name), (check_dbus_user), (run_launch_helper):
+       * bus/activation-helper.h:
+       Add the initial launch-helper. This is split into a main section and a
+       binary loader that allows us to lauch the main section in another test
+       harness to do stuff like OOM testing. No build glue yet.
+
+2007-07-24  Richard Hughes  <richard@hughsie.com>
+
        * bus/Makefile.am:
        * bus/config-parser.c: (bus_config_parser_unref),
        (start_busconfig_child), (bus_config_parser_end_element),
diff --git a/bus/activation-helper-bin.c b/bus/activation-helper-bin.c
new file mode 100644 (file)
index 0000000..6b9ec1f
--- /dev/null
@@ -0,0 +1,92 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* activation-helper-bin.c  Setuid helper for launching programs as a custom
+ *                          user. This file is security sensitive.
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * 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
+ *
+ */
+
+#include <config.h>
+
+#include "utils.h"
+#include "activation-helper.h"
+#include "activation-exit-codes.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int
+convert_error_to_exit_code (DBusError *error)
+{
+  if (dbus_error_has_name (error, DBUS_ERROR_NO_MEMORY))
+    return BUS_SPAWN_EXIT_CODE_NO_MEMORY;
+
+  if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_CONFIG_INVALID))
+    return BUS_SPAWN_EXIT_CODE_CONFIG_INVALID;
+
+  if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_SETUP_FAILED))
+    return BUS_SPAWN_EXIT_CODE_SETUP_FAILED;
+
+  if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_SERVICE_INVALID))
+    return BUS_SPAWN_EXIT_CODE_SERVICE_NOT_FOUND;
+
+  if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID))
+    return BUS_SPAWN_EXIT_CODE_PERMISSIONS_INVALID;
+
+  if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_FILE_INVALID))
+    return BUS_SPAWN_EXIT_CODE_FILE_INVALID;
+
+  if (dbus_error_has_name (error, DBUS_ERROR_SPAWN_EXEC_FAILED))
+    return BUS_SPAWN_EXIT_CODE_EXEC_FAILED;
+
+  /* should we assert? */
+  return BUS_SPAWN_EXIT_CODE_SETUP_FAILED;
+}
+
+int
+main (int argc, char **argv)
+{
+  DBusError error;
+  int retval;
+
+  /* default is all okay */
+  retval = 0;
+
+  /* have we used a help option or not specified the correct arguments? */
+  if (argc != 2 ||
+      strcmp (argv[1], "--help") == 0 ||
+      strcmp (argv[1], "-h") == 0 ||
+      strcmp (argv[1], "-?") == 0)
+    {
+        fprintf (stderr, "dbus-daemon-activation-helper service.to.activate\n");
+        exit (0);
+    }
+
+  dbus_error_init (&error);
+  if (!run_launch_helper (argv[1], &error))
+    {
+      /* convert error to an exit code */
+      retval = convert_error_to_exit_code (&error);
+      dbus_error_free (&error);
+    }
+
+  return retval;
+}
+
diff --git a/bus/activation-helper.c b/bus/activation-helper.c
new file mode 100644 (file)
index 0000000..1636660
--- /dev/null
@@ -0,0 +1,556 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* activation-helper.c  Setuid helper for launching programs as a custom
+ *                      user. This file is security sensitive.
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * 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
+ *
+ */
+
+#include <config.h>
+
+#include "bus.h"
+#include "driver.h"
+#include "utils.h"
+#include "desktop-file.h"
+#include "config-parser-trivial.h"
+#include "activation-helper.h"
+#include "activation-exit-codes.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <dbus/dbus-shell.h>
+#include <dbus/dbus-marshal-validate.h>
+
+static BusDesktopFile *
+desktop_file_for_name (BusConfigParser *parser,
+                       const char *name,
+                       DBusError  *error)
+{
+  BusDesktopFile *desktop_file;
+  DBusList **service_dirs;
+  DBusList *link;
+  DBusError tmp_error;
+  DBusString full_path;
+  DBusString filename;
+  const char *dir;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+  desktop_file = NULL;
+
+  if (!_dbus_string_init (&filename))
+    {
+      BUS_SET_OOM (error);
+      goto out_all;
+    }
+
+  if (!_dbus_string_init (&full_path))
+    {
+      BUS_SET_OOM (error);
+      goto out_filename;
+    }
+
+  if (!_dbus_string_append (&filename, name) ||
+      !_dbus_string_append (&filename, ".service"))
+    {
+      BUS_SET_OOM (error);
+      goto out;
+    }
+
+  service_dirs = bus_config_parser_get_service_dirs (parser);
+  for (link = _dbus_list_get_first_link (service_dirs);
+       link != NULL;
+       link = _dbus_list_get_next_link (service_dirs, link))
+    {
+      dir = link->data;
+      _dbus_verbose ("Looking at '%s'\n", dir);
+
+      dbus_error_init (&tmp_error);
+
+      /* clear the path from last time */
+      _dbus_string_set_length (&full_path, 0);
+
+      /* build the full path */
+      if (!_dbus_string_append (&full_path, dir) ||
+          !_dbus_concat_dir_and_file (&full_path, &filename))
+        {
+          BUS_SET_OOM (error);
+          goto out;
+        }
+
+      _dbus_verbose ("Trying to load file '%s'\n", _dbus_string_get_data (&full_path));
+      desktop_file = bus_desktop_file_load (&full_path, &tmp_error);
+      if (desktop_file == NULL)
+        {
+          _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
+          _dbus_verbose ("Could not load %s: %s: %s\n",
+                         _dbus_string_get_const_data (&full_path),
+                         tmp_error.name, tmp_error.message);
+
+          /* we may have failed if the file is not found; this is not fatal */
+          if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
+            {
+              dbus_move_error (&tmp_error, error);
+              /* we only bail out on OOM */
+              goto out;
+            }
+          dbus_error_free (&tmp_error);
+        }
+
+      /* did we find the desktop file we want? */
+      if (desktop_file != NULL)
+        break;
+    }
+
+  /* Didn't find desktop file; set error */
+  if (desktop_file == NULL)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,
+                      "The name %s was not provided by any .service files",
+                      name);
+    }
+
+out:
+  _dbus_string_free (&full_path);
+out_filename:
+  _dbus_string_free (&filename);
+out_all:
+  return desktop_file;
+}
+
+/* Cleares the environment, except for DBUS_VERBOSE and DBUS_STARTER_x */
+static dbus_bool_t
+clear_environment (DBusError *error)
+{
+  const char *debug_env = NULL;
+  const char *starter_env = NULL;
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+  /* are we debugging */
+  debug_env = _dbus_getenv ("DBUS_VERBOSE");
+#endif
+
+  /* we save the starter */
+  starter_env = _dbus_getenv ("DBUS_STARTER_ADDRESS");
+
+#ifndef ACTIVATION_LAUNCHER_TEST
+  /* totally clear the environment */
+  if (!_dbus_clearenv ())
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
+                      "could not clear environment\n");
+      return FALSE;
+    }
+#endif
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+  /* restore the debugging environment setting if set */
+  if (debug_env)
+    _dbus_setenv ("DBUS_VERBOSE", debug_env);
+#endif
+
+  /* restore the starter */
+  if (starter_env)
+    _dbus_setenv ("DBUS_STARTER_ADDRESS", starter_env);
+
+  /* set the type, which must be system if we got this far */
+  _dbus_setenv ("DBUS_STARTER_BUS_TYPE", "system");
+
+  return TRUE;
+}
+
+static dbus_bool_t
+check_permissions (const char *dbus_user, DBusError *error)
+{
+  uid_t uid, euid;
+  struct passwd *pw;
+
+  pw = NULL;
+  uid = 0;
+  euid = 0;
+
+#ifndef ACTIVATION_LAUNCHER_TEST
+  /* bail out unless the dbus user is invoking the helper */
+  pw = getpwnam(dbus_user);
+  if (!pw)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
+                      "cannot find user '%s'", dbus_user);
+      return FALSE;
+    }
+  uid = getuid();
+  if (pw->pw_uid != uid)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
+                      "not invoked from user '%s'", dbus_user);
+      return FALSE;
+    }
+
+  /* bail out unless we are setuid to user root */
+  euid = geteuid();
+  if (euid != 0)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
+                      "not setuid root");
+      return FALSE;
+    }
+#endif
+
+  return TRUE;
+}
+
+static dbus_bool_t
+check_service_name (BusDesktopFile *desktop_file,
+                    const char     *service_name,
+                    DBusError      *error)
+{
+  char *name_tmp;
+  dbus_bool_t retval;
+
+  retval = FALSE;
+
+  /* try to get Name */
+  if (!bus_desktop_file_get_string (desktop_file,
+                                    DBUS_SERVICE_SECTION,
+                                    DBUS_SERVICE_NAME,
+                                    &name_tmp,
+                                    error))
+    goto failed;
+
+  /* verify that the name is the same as the file service name */
+  if (strcmp (service_name, name_tmp) != 0)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID,
+                      "Service '%s' does not match expected value", name_tmp);
+      goto failed_free;
+    }
+
+  retval = TRUE;
+
+failed_free:
+  /* we don't return the name, so free it here */
+  dbus_free (name_tmp);
+failed:
+  return retval;
+}
+
+static dbus_bool_t
+get_parameters_for_service (BusDesktopFile *desktop_file,
+                            const char     *service_name,
+                            char          **exec,
+                            char          **user,
+                            DBusError      *error)
+{
+  char *exec_tmp;
+  char *user_tmp;
+
+  exec_tmp = NULL;
+  user_tmp = NULL;
+
+  /* check the name of the service */
+  if (!check_service_name (desktop_file, service_name, error))
+    goto failed;
+
+  /* get the complete path of the executable */
+  if (!bus_desktop_file_get_string (desktop_file,
+                                    DBUS_SERVICE_SECTION,
+                                    DBUS_SERVICE_EXEC,
+                                    &exec_tmp,
+                                    error))
+    {
+      _DBUS_ASSERT_ERROR_IS_SET (error);
+      goto failed;
+    }
+
+  /* get the user that should run this service - user is compulsary for system activation */
+  if (!bus_desktop_file_get_string (desktop_file,
+                                    DBUS_SERVICE_SECTION,
+                                    DBUS_SERVICE_USER,
+                                    &user_tmp,
+                                    error))
+    {
+      _DBUS_ASSERT_ERROR_IS_SET (error);
+      goto failed;
+    }
+
+  /* only assign if all the checks passed */
+  *exec = exec_tmp;
+  *user = user_tmp;
+  return TRUE;
+
+failed:
+  dbus_free (exec_tmp);
+  dbus_free (user_tmp);
+  return FALSE;
+}
+
+static dbus_bool_t
+switch_user (char *user, DBusError *error)
+{
+#ifndef ACTIVATION_LAUNCHER_TEST
+  struct passwd *pw;
+
+  /* find user */
+  pw = getpwnam (user);
+  if (!pw)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
+                      "cannot find user '%s'\n", user);
+      return FALSE;
+    }
+
+  /* initialize the group access list */
+  if (initgroups (user, pw->pw_gid))
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
+                      "could not initialize groups");
+      return FALSE;
+    }
+
+  /* change to the primary group for the user */
+  if (setgid (pw->pw_gid))
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
+                      "cannot setgid group %i", pw->pw_gid);
+      return FALSE;
+    }
+
+  /* change to the user specified */
+  if (setuid (pw->pw_uid) < 0)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
+                      "cannot setuid user %i", pw->pw_uid);
+      return FALSE;
+    }
+#endif
+  return TRUE;
+}
+
+static dbus_bool_t
+exec_for_correct_user (char *exec, char *user, DBusError *error)
+{
+  char **argv;
+  int argc;
+  dbus_bool_t retval;
+
+  argc = 0;
+  retval = TRUE;
+  argv = NULL;
+
+  if (!switch_user (user, error))
+    return FALSE;
+
+  /* convert command into arguments */
+  if (!_dbus_shell_parse_argv (exec, &argc, &argv, error))
+    return FALSE;
+
+#ifndef ACTIVATION_LAUNCHER_DO_OOM
+  /* replace with new binary, with no environment */
+  if (execv (argv[0], argv) < 0)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED,
+                      "Failed to exec: %s", argv[0]);
+      retval = FALSE;
+    }
+#endif
+
+  dbus_free_string_array (argv);
+  return retval;
+}
+
+static dbus_bool_t
+check_bus_name (const char *bus_name, DBusError *error)
+{
+  if (!_dbus_check_is_valid_bus_name (bus_name))
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,
+                      "bus name '%s' not found\n", bus_name);
+      return FALSE;
+    }
+  return TRUE;
+}
+
+static dbus_bool_t
+get_correct_parser (BusConfigParser **parser, DBusError *error)
+{
+  DBusString config_file;
+  dbus_bool_t retval;
+  const char *test_config_file;
+
+  retval = FALSE;
+  test_config_file = NULL;
+
+#ifdef ACTIVATION_LAUNCHER_TEST
+  /* there is no _way_ we should be setuid if this define is set.
+   * but we should be doubly paranoid and check... */
+  if (getuid() != geteuid())
+    _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!");
+
+  /* this is not a security hole. The environment variable is only passed in the
+   * dbus-daemon-lauch-helper-test NON-SETUID launcher */
+  test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG");
+  if (test_config_file == NULL)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
+                      "the TEST_LAUNCH_HELPER_CONFIG env variable is not set");
+      goto out;
+    }
+#endif
+
+  /* we _only_ use the predefined system config file */
+  if (!_dbus_string_init (&config_file))
+    {
+      BUS_SET_OOM (error);
+      goto out;
+    }
+#ifndef ACTIVATION_LAUNCHER_TEST
+  if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE))
+    {
+      BUS_SET_OOM (error);
+      goto out_free_config;
+    }
+#else
+  if (!_dbus_string_append (&config_file, test_config_file))
+    {
+      BUS_SET_OOM (error);
+      goto out_free_config;
+    }
+#endif
+
+  /* where are we pointing.... */
+  _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n",
+                 _dbus_string_get_const_data (&config_file));
+
+  /* get the dbus user */
+  *parser = bus_config_load (&config_file, TRUE, NULL, error);
+  if (*parser == NULL)
+    {
+      goto out_free_config;
+    }
+
+  /* woot */
+  retval = TRUE;
+
+out_free_config:
+  _dbus_string_free (&config_file);
+out:
+  return retval;
+}
+
+static dbus_bool_t
+launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error)
+{
+  BusDesktopFile *desktop_file;
+  char *exec, *user;
+  dbus_bool_t retval;
+
+  exec = NULL;
+  user = NULL;
+  retval = FALSE;
+
+  /* get the correct service file for the name we are trying to activate */
+  desktop_file = desktop_file_for_name (parser, bus_name, error);
+  if (desktop_file == NULL)
+    return FALSE;
+
+  /* get exec and user for service name */
+  if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error))
+    goto finish;
+
+  _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name);
+  _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec);
+  _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user);
+
+  /* actually execute */
+  if (!exec_for_correct_user (exec, user, error))
+    goto finish;
+
+  retval = TRUE;
+
+finish:
+  dbus_free (exec);
+  dbus_free (user);
+  bus_desktop_file_free (desktop_file);
+  return retval;
+}
+
+static dbus_bool_t
+check_dbus_user (BusConfigParser *parser, DBusError *error)
+{
+  const char *dbus_user;
+
+  dbus_user = bus_config_parser_get_user (parser);
+  if (dbus_user == NULL)
+    {
+      dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID,
+                      "could not get user from config file\n");
+      return FALSE;
+    }
+
+  /* check to see if permissions are correct */
+  if (!check_permissions (dbus_user, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+dbus_bool_t
+run_launch_helper (const char *bus_name, DBusError *error)
+{
+  BusConfigParser *parser;
+  dbus_bool_t retval;
+
+  parser = NULL;
+  retval = FALSE;
+
+  /* clear the environment, apart from a few select settings */
+  if (!clear_environment (error))
+    goto error;
+
+  /* check to see if we have a valid bus name */
+  if (!check_bus_name (bus_name, error))
+    goto error;
+
+  /* get the correct parser, either the test or default parser */
+  if (!get_correct_parser (&parser, error))
+    goto error;
+
+  /* check we are being invoked by the correct dbus user */
+  if (!check_dbus_user (parser, error))
+    goto error_free_parser;
+
+  /* launch the bus with the service defined user */
+  if (!launch_bus_name (bus_name, parser, error))
+    goto error_free_parser;
+
+  /* woohoo! */
+  retval = TRUE;
+
+error_free_parser:
+  bus_config_parser_unref (parser);
+error:
+  return retval;
+}
+
diff --git a/bus/activation-helper.h b/bus/activation-helper.h
new file mode 100644 (file)
index 0000000..aee5010
--- /dev/null
@@ -0,0 +1,31 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* activation-helper.h  The actual activation helper split from the main
+ *                      function for testing.
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ *
+ * Licensed under the Academic Free License version 2.1
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ * 
+ * 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
+ *
+ */
+
+#ifndef BUS_ACTIVATION_HELPER_H
+#define BUS_ACTIVATION_HELPER_H
+
+dbus_bool_t run_launch_helper (const char *bus_name, DBusError *error);
+
+
+#endif /* BUS_ACTIVATION_HELPER_H */