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 /* Cleares the environment, except for DBUS_VERBOSE and DBUS_STARTER_x */ 144 static dbus_bool_t 145 clear_environment (DBusError *error) 146 { 147 const char *debug_env = NULL; 148 const char *starter_env = NULL; 149 150 #ifdef DBUS_ENABLE_VERBOSE_MODE 151 /* are we debugging */ 152 debug_env = _dbus_getenv ("DBUS_VERBOSE"); 153 #endif 154 155 /* we save the starter */ 156 starter_env = _dbus_getenv ("DBUS_STARTER_ADDRESS"); 157 158 #ifndef ACTIVATION_LAUNCHER_TEST 159 /* totally clear the environment */ 160 if (!_dbus_clearenv ()) 161 { 162 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 163 "could not clear environment\n"); 164 return FALSE; 165 } 166 #endif 167 168 #ifdef DBUS_ENABLE_VERBOSE_MODE 169 /* restore the debugging environment setting if set */ 170 if (debug_env) 171 _dbus_setenv ("DBUS_VERBOSE", debug_env); 172 #endif 173 174 /* restore the starter */ 175 if (starter_env) 176 _dbus_setenv ("DBUS_STARTER_ADDRESS", starter_env); 177 178 /* set the type, which must be system if we got this far */ 179 _dbus_setenv ("DBUS_STARTER_BUS_TYPE", "system"); 180 181 return TRUE; 182 } 183 184 static dbus_bool_t 185 check_permissions (const char *dbus_user, DBusError *error) 186 { 187 uid_t uid, euid; 188 struct passwd *pw; 189 190 pw = NULL; 191 uid = 0; 192 euid = 0; 193 194 #ifndef ACTIVATION_LAUNCHER_TEST 195 /* bail out unless the dbus user is invoking the helper */ 196 pw = getpwnam(dbus_user); 197 if (!pw) 198 { 199 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, 200 "cannot find user '%s'", dbus_user); 201 return FALSE; 202 } 203 uid = getuid(); 204 if (pw->pw_uid != uid) 205 { 206 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, 207 "not invoked from user '%s'", dbus_user); 208 return FALSE; 209 } 210 211 /* bail out unless we are setuid to user root */ 212 euid = geteuid(); 213 if (euid != 0) 214 { 215 dbus_set_error (error, DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, 216 "not setuid root"); 217 return FALSE; 218 } 219 #endif 220 221 return TRUE; 222 } 223 224 static dbus_bool_t 225 check_service_name (BusDesktopFile *desktop_file, 226 const char *service_name, 227 DBusError *error) 228 { 229 char *name_tmp; 230 dbus_bool_t retval; 231 232 retval = FALSE; 233 234 /* try to get Name */ 235 if (!bus_desktop_file_get_string (desktop_file, 236 DBUS_SERVICE_SECTION, 237 DBUS_SERVICE_NAME, 238 &name_tmp, 239 error)) 240 goto failed; 241 242 /* verify that the name is the same as the file service name */ 243 if (strcmp (service_name, name_tmp) != 0) 244 { 245 dbus_set_error (error, DBUS_ERROR_SPAWN_FILE_INVALID, 246 "Service '%s' does not match expected value", name_tmp); 247 goto failed_free; 248 } 249 250 retval = TRUE; 251 252 failed_free: 253 /* we don't return the name, so free it here */ 254 dbus_free (name_tmp); 255 failed: 256 return retval; 257 } 258 259 static dbus_bool_t 260 get_parameters_for_service (BusDesktopFile *desktop_file, 261 const char *service_name, 262 char **exec, 263 char **user, 264 DBusError *error) 265 { 266 char *exec_tmp; 267 char *user_tmp; 268 269 exec_tmp = NULL; 270 user_tmp = NULL; 271 272 /* check the name of the service */ 273 if (!check_service_name (desktop_file, service_name, error)) 274 goto failed; 275 276 /* get the complete path of the executable */ 277 if (!bus_desktop_file_get_string (desktop_file, 278 DBUS_SERVICE_SECTION, 279 DBUS_SERVICE_EXEC, 280 &exec_tmp, 281 error)) 282 { 283 _DBUS_ASSERT_ERROR_IS_SET (error); 284 goto failed; 285 } 286 287 /* get the user that should run this service - user is compulsary for system activation */ 288 if (!bus_desktop_file_get_string (desktop_file, 289 DBUS_SERVICE_SECTION, 290 DBUS_SERVICE_USER, 291 &user_tmp, 292 error)) 293 { 294 _DBUS_ASSERT_ERROR_IS_SET (error); 295 goto failed; 296 } 297 298 /* only assign if all the checks passed */ 299 *exec = exec_tmp; 300 *user = user_tmp; 301 return TRUE; 302 303 failed: 304 dbus_free (exec_tmp); 305 dbus_free (user_tmp); 306 return FALSE; 307 } 308 309 static dbus_bool_t 310 switch_user (char *user, DBusError *error) 311 { 312 #ifndef ACTIVATION_LAUNCHER_TEST 313 struct passwd *pw; 314 315 /* find user */ 316 pw = getpwnam (user); 317 if (!pw) 318 { 319 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 320 "cannot find user '%s'\n", user); 321 return FALSE; 322 } 323 324 /* initialize the group access list */ 325 if (initgroups (user, pw->pw_gid)) 326 { 327 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 328 "could not initialize groups"); 329 return FALSE; 330 } 331 332 /* change to the primary group for the user */ 333 if (setgid (pw->pw_gid)) 334 { 335 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 336 "cannot setgid group %i", pw->pw_gid); 337 return FALSE; 338 } 339 340 /* change to the user specified */ 341 if (setuid (pw->pw_uid) < 0) 342 { 343 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 344 "cannot setuid user %i", pw->pw_uid); 345 return FALSE; 346 } 347 #endif 348 return TRUE; 349 } 350 351 static dbus_bool_t 352 exec_for_correct_user (char *exec, char *user, DBusError *error) 353 { 354 char **argv; 355 int argc; 356 dbus_bool_t retval; 357 358 argc = 0; 359 retval = TRUE; 360 argv = NULL; 361 362 if (!switch_user (user, error)) 363 return FALSE; 364 365 /* convert command into arguments */ 366 if (!_dbus_shell_parse_argv (exec, &argc, &argv, error)) 367 return FALSE; 368 369 #ifndef ACTIVATION_LAUNCHER_DO_OOM 370 /* replace with new binary, with no environment */ 371 if (execv (argv[0], argv) < 0) 372 { 373 dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, 374 "Failed to exec: %s", argv[0]); 375 retval = FALSE; 376 } 377 #endif 378 379 dbus_free_string_array (argv); 380 return retval; 381 } 382 383 static dbus_bool_t 384 check_bus_name (const char *bus_name, 385 DBusError *error) 386 { 387 DBusString str; 388 389 _dbus_string_init_const (&str, bus_name); 390 if (!_dbus_validate_bus_name (&str, 0, _dbus_string_get_length (&str))) 391 { 392 dbus_set_error (error, DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, 393 "bus name '%s' is not a valid bus name\n", 394 bus_name); 395 return FALSE; 396 } 397 398 return TRUE; 399 } 400 401 static dbus_bool_t 402 get_correct_parser (BusConfigParser **parser, DBusError *error) 403 { 404 DBusString config_file; 405 dbus_bool_t retval; 406 const char *test_config_file; 407 408 retval = FALSE; 409 test_config_file = NULL; 410 411 #ifdef ACTIVATION_LAUNCHER_TEST 412 /* there is no _way_ we should be setuid if this define is set. 413 * but we should be doubly paranoid and check... */ 414 if (getuid() != geteuid()) 415 _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!"); 416 417 /* this is not a security hole. The environment variable is only passed in the 418 * dbus-daemon-lauch-helper-test NON-SETUID launcher */ 419 test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG"); 420 if (test_config_file == NULL) 421 { 422 dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED, 423 "the TEST_LAUNCH_HELPER_CONFIG env variable is not set"); 424 goto out; 425 } 426 #endif 427 428 /* we _only_ use the predefined system config file */ 429 if (!_dbus_string_init (&config_file)) 430 { 431 BUS_SET_OOM (error); 432 goto out; 433 } 434 #ifndef ACTIVATION_LAUNCHER_TEST 435 if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE)) 436 { 437 BUS_SET_OOM (error); 438 goto out_free_config; 439 } 440 #else 441 if (!_dbus_string_append (&config_file, test_config_file)) 442 { 443 BUS_SET_OOM (error); 444 goto out_free_config; 445 } 446 #endif 447 448 /* where are we pointing.... */ 449 _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n", 450 _dbus_string_get_const_data (&config_file)); 451 452 /* get the dbus user */ 453 *parser = bus_config_load (&config_file, TRUE, NULL, error); 454 if (*parser == NULL) 455 { 456 goto out_free_config; 457 } 458 459 /* woot */ 460 retval = TRUE; 461 462 out_free_config: 463 _dbus_string_free (&config_file); 464 out: 465 return retval; 466 } 467 468 static dbus_bool_t 469 launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error) 470 { 471 BusDesktopFile *desktop_file; 472 char *exec, *user; 473 dbus_bool_t retval; 474 475 exec = NULL; 476 user = NULL; 477 retval = FALSE; 478 479 /* get the correct service file for the name we are trying to activate */ 480 desktop_file = desktop_file_for_name (parser, bus_name, error); 481 if (desktop_file == NULL) 482 return FALSE; 483 484 /* get exec and user for service name */ 485 if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error)) 486 goto finish; 487 488 _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name); 489 _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec); 490 _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user); 491 492 /* actually execute */ 493 if (!exec_for_correct_user (exec, user, error)) 494 goto finish; 495 496 retval = TRUE; 497 498 finish: 499 dbus_free (exec); 500 dbus_free (user); 501 bus_desktop_file_free (desktop_file); 502 return retval; 503 } 504 505 static dbus_bool_t 506 check_dbus_user (BusConfigParser *parser, DBusError *error) 507 { 508 const char *dbus_user; 509 510 dbus_user = bus_config_parser_get_user (parser); 511 if (dbus_user == NULL) 512 { 513 dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID, 514 "could not get user from config file\n"); 515 return FALSE; 516 } 517 518 /* check to see if permissions are correct */ 519 if (!check_permissions (dbus_user, error)) 520 return FALSE; 521 522 return TRUE; 523 } 524 525 dbus_bool_t 526 run_launch_helper (const char *bus_name, 527 DBusError *error) 528 { 529 BusConfigParser *parser; 530 dbus_bool_t retval; 531 532 parser = NULL; 533 retval = FALSE; 534 535 /* clear the environment, apart from a few select settings */ 536 if (!clear_environment (error)) 537 goto error; 538 539 /* check to see if we have a valid bus name */ 540 if (!check_bus_name (bus_name, error)) 541 goto error; 542 543 /* get the correct parser, either the test or default parser */ 544 if (!get_correct_parser (&parser, error)) 545 goto error; 546 547 /* check we are being invoked by the correct dbus user */ 548 if (!check_dbus_user (parser, error)) 549 goto error_free_parser; 550 551 /* launch the bus with the service defined user */ 552 if (!launch_bus_name (bus_name, parser, error)) 553 goto error_free_parser; 554 555 /* woohoo! */ 556 retval = TRUE; 557 558 error_free_parser: 559 bus_config_parser_unref (parser); 560 error: 561 return retval; 562 } 563 564