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. 4 * 5 * Copyright (C) 2007 Red Hat, Inc. 6 * 7 * Licensed under the Academic Free License version 2.1 8 * 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. 13 * 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. 18 * 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 22 * 23 */ 24 25 #include <config.h> 26 27 #include "bus.h" 28 #include "driver.h" 29 #include "utils.h" 30 #include "desktop-file.h" 31 #include "config-parser-trivial.h" 32 #include "activation-helper.h" 33 #include "activation-exit-codes.h" 34 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 #include <sys/types.h> 40 #include <pwd.h> 41 #include <grp.h> 42 43 #include <dbus/dbus-shell.h> 44 #include <dbus/dbus-marshal-validate.h> 45 46 static BusDesktopFile * 47 desktop_file_for_name (BusConfigParser *parser, 48 const char *name, 49 DBusError *error) 50 { 51 BusDesktopFile *desktop_file; 52 DBusList **service_dirs; 53 DBusList *link; 54 DBusError tmp_error; 55 DBusString full_path; 56 DBusString filename; 57 const char *dir; 58 59 _DBUS_ASSERT_ERROR_IS_CLEAR (error); 60 61 desktop_file = NULL; 62 63 if (!_dbus_string_init (&filename)) 64 { 65 BUS_SET_OOM (error); 66 goto out_all; 67 } 68 69 if (!_dbus_string_init (&full_path)) 70 { 71 BUS_SET_OOM (error); 72 goto out_filename; 73 } 74 75 if (!_dbus_string_append (&filename, name) || 76 !_dbus_string_append (&filename, ".service")) 77 { 78 BUS_SET_OOM (error); 79 goto out; 80 } 81 82 service_dirs = bus_config_parser_get_service_dirs (parser); 83 for (link = _dbus_list_get_first_link (service_dirs); 84 link != NULL; 85 link = _dbus_list_get_next_link (service_dirs, link)) 86 { 87 dir = link->data; 88 _dbus_verbose ("Looking at '%s'\n", dir); 89 90 dbus_error_init (&tmp_error); 91 92 /* clear the path from last time */ 93 _dbus_string_set_length (&full_path, 0); 94 95 /* build the full path */ 96 if (!_dbus_string_append (&full_path, dir) || 97 !_dbus_concat_dir_and_file (&full_path, &filename)) 98 { 99 BUS_SET_OOM (error); 100 goto out; 101 } 102 103 _dbus_verbose ("Trying to load file '%s'\n", _dbus_string_get_data (&full_path)); 104 desktop_file = bus_desktop_file_load (&full_path, &tmp_error); 105 if (desktop_file == NULL) 106 { 107 _DBUS_ASSERT_ERROR_IS_SET (&tmp_error); 108 _dbus_verbose ("Could not load %s: %s: %s\n", 109 _dbus_string_get_const_data (&full_path), 110 tmp_error.name, tmp_error.message); 111 112 /* we may have failed if the file is not found; this is not fatal */ 113 if (dbus_error_has_name (&tmp_error, DBUS_ERROR_NO_MEMORY)) 114 { 115 dbus_move_error (&tmp_error, error); 116 /* we only bail out on OOM */ 117 goto out; 118 } 119 dbus_error_free (&tmp_error); 120 } 121 122 /* did we find the desktop file we want? */ 123 if (desktop_file != NULL) 124 break; 125 } 126 127 /* Didn't find desktop file; set error */ 128 if (desktop_file == NULL) 129 { 130 dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, 131 "The name %s was not provided by any .service files", 132 name); 133 } 134 135 out: 136 _dbus_string_free (&full_path); 137 out_filename: 138 _dbus_string_free (&filename); 139 out_all: 140 return desktop_file; 141 } 142 143 /* Clears the environment, except for DBUS_STARTER_x, 144 * which we hardcode to the system bus. 145 */ 146 static dbus_bool_t 147 clear_environment (DBusError *error) 148 { 149 #ifndef ACTIVATION_LAUNCHER_TEST 150 /* totally clear the environment */ 151 if (!_dbus_clearenv ()) 152 { 153 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 154 "could not clear environment\n"); 155 return FALSE; 156 } 157 #endif 158 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"); 162 163 return TRUE; 164 } 165 166 static dbus_bool_t 167 check_permissions (const char *dbus_user, DBusError *error) 168 { 169 #ifndef ACTIVATION_LAUNCHER_TEST 170 uid_t uid, euid; 171 struct passwd *pw; 172 173 pw = NULL; 174 uid = 0; 175 euid = 0; 176 177 /* bail out unless the dbus user is invoking the helper */ 178 pw = getpwnam(dbus_user); 179 if (!pw) 180 { 181 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, 182 "cannot find user '%s'", dbus_user); 183 return FALSE; 184 } 185 uid = getuid(); 186 if (pw->pw_uid != uid) 187 { 188 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, 189 "not invoked from user '%s'", dbus_user); 190 return FALSE; 191 } 192 193 /* bail out unless we are setuid to user root */ 194 euid = geteuid(); 195 if (euid != 0) 196 { 197 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, 198 "not setuid root"); 199 return FALSE; 200 } 201 #endif 202 203 return TRUE; 204 } 205 206 static dbus_bool_t 207 check_service_name (BusDesktopFile *desktop_file, 208 const char *service_name, 209 DBusError *error) 210 { 211 char *name_tmp; 212 dbus_bool_t retval; 213 214 retval = FALSE; 215 216 /* try to get Name */ 217 if (!bus_desktop_file_get_string (desktop_file, 218 DBUS_SERVICE_SECTION, 219 DBUS_SERVICE_NAME, 220 &name_tmp, 221 error)) 222 goto failed; 223 224 /* verify that the name is the same as the file service name */ 225 if (strcmp (service_name, name_tmp) != 0) 226 { 227 dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID, 228 "Service '%s' does not match expected value", name_tmp); 229 goto failed_free; 230 } 231 232 retval = TRUE; 233 234 failed_free: 235 /* we don't return the name, so free it here */ 236 dbus_free (name_tmp); 237 failed: 238 return retval; 239 } 240 241 static dbus_bool_t 242 get_parameters_for_service (BusDesktopFile *desktop_file, 243 const char *service_name, 244 char **exec, 245 char **user, 246 DBusError *error) 247 { 248 char *exec_tmp; 249 char *user_tmp; 250 251 exec_tmp = NULL; 252 user_tmp = NULL; 253 254 /* check the name of the service */ 255 if (!check_service_name (desktop_file, service_name, error)) 256 goto failed; 257 258 /* get the complete path of the executable */ 259 if (!bus_desktop_file_get_string (desktop_file, 260 DBUS_SERVICE_SECTION, 261 DBUS_SERVICE_EXEC, 262 &exec_tmp, 263 error)) 264 { 265 _DBUS_ASSERT_ERROR_IS_SET (error); 266 goto failed; 267 } 268 269 /* get the user that should run this service - user is compulsary for system activation */ 270 if (!bus_desktop_file_get_string (desktop_file, 271 DBUS_SERVICE_SECTION, 272 DBUS_SERVICE_USER, 273 &user_tmp, 274 error)) 275 { 276 _DBUS_ASSERT_ERROR_IS_SET (error); 277 goto failed; 278 } 279 280 /* only assign if all the checks passed */ 281 *exec = exec_tmp; 282 *user = user_tmp; 283 return TRUE; 284 285 failed: 286 dbus_free (exec_tmp); 287 dbus_free (user_tmp); 288 return FALSE; 289 } 290 291 static dbus_bool_t 292 switch_user (char *user, DBusError *error) 293 { 294 #ifndef ACTIVATION_LAUNCHER_TEST 295 struct passwd *pw; 296 297 /* find user */ 298 pw = getpwnam (user); 299 if (!pw) 300 { 301 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 302 "cannot find user '%s'\n", user); 303 return FALSE; 304 } 305 306 /* initialize the group access list */ 307 if (initgroups (user, pw->pw_gid)) 308 { 309 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 310 "could not initialize groups"); 311 return FALSE; 312 } 313 314 /* change to the primary group for the user */ 315 if (setgid (pw->pw_gid)) 316 { 317 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 318 "cannot setgid group %i", pw->pw_gid); 319 return FALSE; 320 } 321 322 /* change to the user specified */ 323 if (setuid (pw->pw_uid) < 0) 324 { 325 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 326 "cannot setuid user %i", pw->pw_uid); 327 return FALSE; 328 } 329 #endif 330 return TRUE; 331 } 332 333 static dbus_bool_t 334 exec_for_correct_user (char *exec, char *user, DBusError *error) 335 { 336 char **argv; 337 int argc; 338 dbus_bool_t retval; 339 340 argc = 0; 341 retval = TRUE; 342 argv = NULL; 343 344 if (!switch_user (user, error)) 345 return FALSE; 346 347 /* convert command into arguments */ 348 if (!_dbus_shell_parse_argv (exec, &argc, &argv, error)) 349 return FALSE; 350 351 #ifndef ACTIVATION_LAUNCHER_DO_OOM 352 /* replace with new binary, with no environment */ 353 if (execv (argv[0], argv) < 0) 354 { 355 dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, 356 "Failed to exec: %s", argv[0]); 357 retval = FALSE; 358 } 359 #endif 360 361 dbus_free_string_array (argv); 362 return retval; 363 } 364 365 static dbus_bool_t 366 check_bus_name (const char *bus_name, 367 DBusError *error) 368 { 369 DBusString str; 370 371 _dbus_string_init_const (&str, bus_name); 372 if (!_dbus_validate_bus_name (&str, 0, _dbus_string_get_length (&str))) 373 { 374 dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, 375 "bus name '%s' is not a valid bus name\n", 376 bus_name); 377 return FALSE; 378 } 379 380 return TRUE; 381 } 382 383 static dbus_bool_t 384 get_correct_parser (BusConfigParser **parser, DBusError *error) 385 { 386 DBusString config_file; 387 dbus_bool_t retval; 388 #ifdef ACTIVATION_LAUNCHER_TEST 389 const char *test_config_file; 390 #endif 391 392 retval = FALSE; 393 394 #ifdef ACTIVATION_LAUNCHER_TEST 395 test_config_file = NULL; 396 397 /* there is no _way_ we should be setuid if this define is set. 398 * but we should be doubly paranoid and check... */ 399 if (getuid() != geteuid()) 400 _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!"); 401 402 /* this is not a security hole. The environment variable is only passed in the 403 * dbus-daemon-lauch-helper-test NON-SETUID launcher */ 404 test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG"); 405 if (test_config_file == NULL) 406 { 407 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 408 "the TEST_LAUNCH_HELPER_CONFIG env variable is not set"); 409 goto out; 410 } 411 #endif 412 413 /* we _only_ use the predefined system config file */ 414 if (!_dbus_string_init (&config_file)) 415 { 416 BUS_SET_OOM (error); 417 goto out; 418 } 419 #ifndef ACTIVATION_LAUNCHER_TEST 420 if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE)) 421 { 422 BUS_SET_OOM (error); 423 goto out_free_config; 424 } 425 #else 426 if (!_dbus_string_append (&config_file, test_config_file)) 427 { 428 BUS_SET_OOM (error); 429 goto out_free_config; 430 } 431 #endif 432 433 /* where are we pointing.... */ 434 _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n", 435 _dbus_string_get_const_data (&config_file)); 436 437 /* get the dbus user */ 438 *parser = bus_config_load (&config_file, TRUE, NULL, error); 439 if (*parser == NULL) 440 { 441 goto out_free_config; 442 } 443 444 /* woot */ 445 retval = TRUE; 446 447 out_free_config: 448 _dbus_string_free (&config_file); 449 out: 450 return retval; 451 } 452 453 static dbus_bool_t 454 launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error) 455 { 456 BusDesktopFile *desktop_file; 457 char *exec, *user; 458 dbus_bool_t retval; 459 460 exec = NULL; 461 user = NULL; 462 retval = FALSE; 463 464 /* get the correct service file for the name we are trying to activate */ 465 desktop_file = desktop_file_for_name (parser, bus_name, error); 466 if (desktop_file == NULL) 467 return FALSE; 468 469 /* get exec and user for service name */ 470 if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error)) 471 goto finish; 472 473 _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name); 474 _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec); 475 _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user); 476 477 /* actually execute */ 478 if (!exec_for_correct_user (exec, user, error)) 479 goto finish; 480 481 retval = TRUE; 482 483 finish: 484 dbus_free (exec); 485 dbus_free (user); 486 bus_desktop_file_free (desktop_file); 487 return retval; 488 } 489 490 static dbus_bool_t 491 check_dbus_user (BusConfigParser *parser, DBusError *error) 492 { 493 const char *dbus_user; 494 495 dbus_user = bus_config_parser_get_user (parser); 496 if (dbus_user == NULL) 497 { 498 dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID, 499 "could not get user from config file\n"); 500 return FALSE; 501 } 502 503 /* check to see if permissions are correct */ 504 if (!check_permissions (dbus_user, error)) 505 return FALSE; 506 507 return TRUE; 508 } 509 510 dbus_bool_t 511 run_launch_helper (const char *bus_name, 512 DBusError *error) 513 { 514 BusConfigParser *parser; 515 dbus_bool_t retval; 516 517 parser = NULL; 518 retval = FALSE; 519 520 /* clear the environment, apart from a few select settings */ 521 if (!clear_environment (error)) 522 goto error; 523 524 /* check to see if we have a valid bus name */ 525 if (!check_bus_name (bus_name, error)) 526 goto error; 527 528 /* get the correct parser, either the test or default parser */ 529 if (!get_correct_parser (&parser, error)) 530 goto error; 531 532 /* check we are being invoked by the correct dbus user */ 533 if (!check_dbus_user (parser, error)) 534 goto error_free_parser; 535 536 /* launch the bus with the service defined user */ 537 if (!launch_bus_name (bus_name, parser, error)) 538 goto error_free_parser; 539 540 /* woohoo! */ 541 retval = TRUE; 542 543 error_free_parser: 544 bus_config_parser_unref (parser); 545 error: 546 return retval; 547 } 548 549