spec: document the search path for session services
[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-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 *starter_env = NULL;
148 #ifdef DBUS_ENABLE_VERBOSE_MODE
149   const char *debug_env = NULL;
150
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 #ifndef ACTIVATION_LAUNCHER_TEST
188   uid_t uid, euid;
189   struct passwd *pw;
190
191   pw = NULL;
192   uid = 0;
193   euid = 0;
194
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 #ifdef ACTIVATION_LAUNCHER_TEST
407   const char *test_config_file;
408 #endif
409
410   retval = FALSE;
411
412 #ifdef ACTIVATION_LAUNCHER_TEST
413   test_config_file = NULL;
414
415   /* there is no _way_ we should be setuid if this define is set.
416    * but we should be doubly paranoid and check... */
417   if (getuid() != geteuid())
418     _dbus_assert_not_reached ("dbus-daemon-launch-helper-test binary is setuid!");
419
420   /* this is not a security hole. The environment variable is only passed in the
421    * dbus-daemon-lauch-helper-test NON-SETUID launcher */
422   test_config_file = _dbus_getenv ("TEST_LAUNCH_HELPER_CONFIG");
423   if (test_config_file == NULL)
424     {
425       dbus_set_error (error, DBUS_ERROR_SPAWN_SETUP_FAILED,
426                       "the TEST_LAUNCH_HELPER_CONFIG env variable is not set");
427       goto out;
428     }
429 #endif
430
431   /* we _only_ use the predefined system config file */
432   if (!_dbus_string_init (&config_file))
433     {
434       BUS_SET_OOM (error);
435       goto out;
436     }
437 #ifndef ACTIVATION_LAUNCHER_TEST
438   if (!_dbus_string_append (&config_file, DBUS_SYSTEM_CONFIG_FILE))
439     {
440       BUS_SET_OOM (error);
441       goto out_free_config;
442     }
443 #else
444   if (!_dbus_string_append (&config_file, test_config_file))
445     {
446       BUS_SET_OOM (error);
447       goto out_free_config;
448     }
449 #endif
450
451   /* where are we pointing.... */
452   _dbus_verbose ("dbus-daemon-activation-helper: using config file: %s\n",
453                  _dbus_string_get_const_data (&config_file));
454
455   /* get the dbus user */
456   *parser = bus_config_load (&config_file, TRUE, NULL, error);
457   if (*parser == NULL)
458     {
459       goto out_free_config;
460     }
461
462   /* woot */
463   retval = TRUE;
464
465 out_free_config:
466   _dbus_string_free (&config_file);
467 out:
468   return retval;
469 }
470
471 static dbus_bool_t
472 launch_bus_name (const char *bus_name, BusConfigParser *parser, DBusError *error)
473 {
474   BusDesktopFile *desktop_file;
475   char *exec, *user;
476   dbus_bool_t retval;
477
478   exec = NULL;
479   user = NULL;
480   retval = FALSE;
481
482   /* get the correct service file for the name we are trying to activate */
483   desktop_file = desktop_file_for_name (parser, bus_name, error);
484   if (desktop_file == NULL)
485     return FALSE;
486
487   /* get exec and user for service name */
488   if (!get_parameters_for_service (desktop_file, bus_name, &exec, &user, error))
489     goto finish;
490
491   _dbus_verbose ("dbus-daemon-activation-helper: Name='%s'\n", bus_name);
492   _dbus_verbose ("dbus-daemon-activation-helper: Exec='%s'\n", exec);
493   _dbus_verbose ("dbus-daemon-activation-helper: User='%s'\n", user);
494
495   /* actually execute */
496   if (!exec_for_correct_user (exec, user, error))
497     goto finish;
498
499   retval = TRUE;
500
501 finish:
502   dbus_free (exec);
503   dbus_free (user);
504   bus_desktop_file_free (desktop_file);
505   return retval;
506 }
507
508 static dbus_bool_t
509 check_dbus_user (BusConfigParser *parser, DBusError *error)
510 {
511   const char *dbus_user;
512
513   dbus_user = bus_config_parser_get_user (parser);
514   if (dbus_user == NULL)
515     {
516       dbus_set_error (error, DBUS_ERROR_SPAWN_CONFIG_INVALID,
517                       "could not get user from config file\n");
518       return FALSE;
519     }
520
521   /* check to see if permissions are correct */
522   if (!check_permissions (dbus_user, error))
523     return FALSE;
524
525   return TRUE;
526 }
527
528 dbus_bool_t
529 run_launch_helper (const char *bus_name,
530                    DBusError  *error)
531 {
532   BusConfigParser *parser;
533   dbus_bool_t retval;
534
535   parser = NULL;
536   retval = FALSE;
537
538   /* clear the environment, apart from a few select settings */
539   if (!clear_environment (error))
540     goto error;
541
542   /* check to see if we have a valid bus name */
543   if (!check_bus_name (bus_name, error))
544     goto error;
545
546   /* get the correct parser, either the test or default parser */
547   if (!get_correct_parser (&parser, error))
548     goto error;
549
550   /* check we are being invoked by the correct dbus user */
551   if (!check_dbus_user (parser, error))
552     goto error_free_parser;
553
554   /* launch the bus with the service defined user */
555   if (!launch_bus_name (bus_name, parser, error))
556     goto error_free_parser;
557
558   /* woohoo! */
559   retval = TRUE;
560
561 error_free_parser:
562   bus_config_parser_unref (parser);
563 error:
564   return retval;
565 }
566