/* -*- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #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 #include #include #include #include #include #include #include #include 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) { DBusString str; _dbus_string_init_const (&str, bus_name); if (!_dbus_validate_bus_name (&str, 0, _dbus_string_get_length (&str))) { dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, "bus name '%s' is not a valid bus name\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; }