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 /* 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