Home | History | Annotate | Download | only in bus
      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