1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* activation-helper.c Setuid helper for launching programs as a custom
3 * user. This file is security sensitive.
5 * Copyright (C) 2007 Red Hat, Inc.
7 * Licensed under the Academic Free License version 2.1
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30 #include "desktop-file.h"
31 #include "config-parser-trivial.h"
32 #include "activation-helper.h"
33 #include "activation-exit-codes.h"
39 #include <sys/types.h>
43 #include <dbus/dbus-misc.h>
44 #include <dbus/dbus-shell.h>
45 #include <dbus/dbus-marshal-validate.h>
47 static BusDesktopFile *
48 desktop_file_for_name (BusConfigParser *parser,
52 BusDesktopFile *desktop_file;
53 DBusList **service_dirs;
60 _DBUS_ASSERT_ERROR_IS_CLEAR (error);
64 if (!_dbus_string_init (&filename))
70 if (!_dbus_string_init (&full_path))
76 if (!_dbus_string_append (&filename, name) ||
77 !_dbus_string_append (&filename, ".service"))
83 service_dirs = bus_config_parser_get_service_paths (parser);
84 for (link = _dbus_list_get_first_link (service_dirs);
86 link = _dbus_list_get_next_link (service_dirs, link))
89 _dbus_verbose ("Looking at '%s'\n", dir);
91 dbus_error_init (&tmp_error);
93 /* clear the path from last time */
94 _dbus_string_set_length (&full_path, 0);
96 /* build the full path */
97 if (!_dbus_string_append (&full_path, dir) ||
98 !_dbus_concat_dir_and_file (&full_path, &filename))
104 _dbus_verbose ("Trying to load file '%s'\n", _dbus_string_get_data (&full_path));
105 desktop_file = bus_desktop_file_load (&full_path, &tmp_error);
106 if (desktop_file == NULL)
108 _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
109 _dbus_verbose ("Could not load %s: %s: %s\n",
110 _dbus_string_get_const_data (&full_path),
111 tmp_error.name, tmp_error.message);
113 /* we may have failed if the file is not found; this is not fatal */
114 if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY))
116 dbus_move_error (&tmp_error, error);
117 /* we only bail out on OOM */
120 dbus_error_free (&tmp_error);
123 /* did we find the desktop file we want? */
124 if (desktop_file != NULL)
128 /* Didn't find desktop file; set error */
129 if (desktop_file == NULL)
131 dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,
132 "The name %s was not provided by any .service files",
137 _dbus_string_free (&full_path);
139 _dbus_string_free (&filename);
144 /* Clears the environment, except for DBUS_STARTER_x,
145 * which we hardcode to the system bus.
148 clear_environment (DBusError *error)
150 #ifndef ACTIVATION_LAUNCHER_TEST
151 /* totally clear the environment */
152 if (!_dbus_clearenv ())
154 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
155 "could not clear environment\n");
159 /* Ensure the bus is set to system */
160 dbus_setenv ("DBUS_STARTER_ADDRESS", DBUS_SYSTEM_BUS_DEFAULT_ADDRESS);
161 dbus_setenv ("DBUS_STARTER_BUS_TYPE", "system");
168 check_permissions (const char *dbus_user, DBusError *error)
170 #ifndef ACTIVATION_LAUNCHER_TEST
178 /* bail out unless the dbus user is invoking the helper */
179 pw = getpwnam(dbus_user);
182 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
183 "cannot find user '%s'", dbus_user);
187 if (pw->pw_uid != uid)
189 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
190 "not invoked from user '%s'", dbus_user);
194 /* bail out unless we are setuid to user root */
198 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,
208 check_service_name (BusDesktopFile *desktop_file,
209 const char *service_name,
217 /* try to get Name */
218 if (!bus_desktop_file_get_string (desktop_file,
219 DBUS_SERVICE_SECTION,
225 /* verify that the name is the same as the file service name */
226 if (strcmp (service_name, name_tmp) != 0)
228 dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID,
229 "Service '%s' does not match expected value", name_tmp);
236 /* we don't return the name, so free it here */
237 dbus_free (name_tmp);
243 get_parameters_for_service (BusDesktopFile *desktop_file,
244 const char *service_name,
255 /* check the name of the service */
256 if (!check_service_name (desktop_file, service_name, error))
259 /* get the complete path of the executable */
260 if (!bus_desktop_file_get_string (desktop_file,
261 DBUS_SERVICE_SECTION,
266 _DBUS_ASSERT_ERROR_IS_SET (error);
270 /* get the user that should run this service - user is compulsary for system activation */
271 if (!bus_desktop_file_get_string (desktop_file,
272 DBUS_SERVICE_SECTION,
277 _DBUS_ASSERT_ERROR_IS_SET (error);
281 /* only assign if all the checks passed */
287 dbus_free (exec_tmp);
288 dbus_free (user_tmp);
293 switch_user (char *user, DBusError *error)
295 #ifndef ACTIVATION_LAUNCHER_TEST
299 pw = getpwnam (user);
302 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
303 "cannot find user '%s'\n", user);
307 /* initialize the group access list */
308 if (initgroups (user, pw->pw_gid))
310 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
311 "could not initialize groups");
315 /* change to the primary group for the user */
316 if (setgid (pw->pw_gid))
318 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
319 "cannot setgid group %i", pw->pw_gid);
323 /* change to the user specified */
324 if (setuid (pw->pw_uid) < 0)
326 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
327 "cannot setuid user %i", pw->pw_uid);
335 exec_for_correct_user (char *exec, char *user, DBusError *error)
345 if (!switch_user (user, error))
348 /* convert command into arguments */
349 if (!_dbus_shell_parse_argv (exec, &argc, &argv, error))
352 #ifndef ACTIVATION_LAUNCHER_DO_OOM
353 /* replace with new binary, with no environment */
354 if (execv (argv[0], argv) < 0)
356 dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED,
357 "Failed to exec: %s", argv[0]);
362 dbus_free_string_array (argv);
367 check_bus_name (const char *bus_name,
372 _dbus_string_init_const (&str, bus_name);
373 if (!_dbus_validate_bus_name (&str, 0, _dbus_string_get_length (&str)))
375 dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_INVALID,
376 "bus name '%s' is not a valid bus name\n",
385 get_correct_parser (BusConfigParser **parser, DBusError *error)
387 DBusString config_file;
389 #ifdef ACTIVATION_LAUNCHER_TEST
390 const char *test_config_file;
395 #ifdef ACTIVATION_LAUNCHER_TEST
396 test_config_file = NULL;
398 /* there is no _way_ we should be setuid if this define is set.
399 * but we should be doubly paranoid and check... */
400 if (getuid() != geteuid())
401 _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!");
403 /* this is not a security hole. The environment variable is only passed in the
404 * dbus-daemon-lauch-helper-test NON-SETUID launcher */
405 test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG");
406 if (test_config_file == NULL)
408 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
409 "the TEST_LAUNCH_HELPER_CONFIG env variable is not set");
414 /* we _only_ use the predefined system config file */
415 if (!_dbus_string_init (&config_file))
420 #ifndef ACTIVATION_LAUNCHER_TEST
421 if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE))
424 goto out_free_config;
427 if (!_dbus_string_append (&config_file, test_config_file))
430 goto out_free_config;
434 /* where are we pointing.... */
435 _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n",
436 _dbus_string_get_const_data (&config_file));
438 /* get the dbus user */
439 *parser = bus_config_load (&config_file, TRUE, NULL, error);
442 goto out_free_config;
449 _dbus_string_free (&config_file);
455 launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error)
457 BusDesktopFile *desktop_file;
465 /* get the correct service file for the name we are trying to activate */
466 desktop_file = desktop_file_for_name (parser, bus_name, error);
467 if (desktop_file == NULL)
470 /* get exec and user for service name */
471 if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error))
474 _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name);
475 _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec);
476 _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user);
478 /* actually execute */
479 if (!exec_for_correct_user (exec, user, error))
487 bus_desktop_file_free (desktop_file);
492 check_dbus_user (BusConfigParser *parser, DBusError *error)
494 const char *dbus_user;
496 dbus_user = bus_config_parser_get_user (parser);
497 if (dbus_user == NULL)
499 dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID,
500 "could not get user from config file\n");
504 /* check to see if permissions are correct */
505 if (!check_permissions (dbus_user, error))
512 run_launch_helper (const char *bus_name,
515 BusConfigParser *parser;
521 /* clear the environment, apart from a few select settings */
522 if (!clear_environment (error))
525 /* check to see if we have a valid bus name */
526 if (!check_bus_name (bus_name, error))
529 /* get the correct parser, either the test or default parser */
530 if (!get_correct_parser (&parser, error))
533 /* check we are being invoked by the correct dbus user */
534 if (!check_dbus_user (parser, error))
535 goto error_free_parser;
537 /* launch the bus with the service defined user */
538 if (!launch_bus_name (bus_name, parser, error))
539 goto error_free_parser;
545 bus_config_parser_unref (parser);