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